fugit 1.3.5 → 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: d2a28ef486584f37659fbcf4e6e4fecfc76aa2a4a6e1fc61d25dd3c44cd3a5c3
4
- data.tar.gz: db726ca071f0f98d0f59c3054fca04a537ec2a17fd302013b354d90e1d7dc3c8
3
+ metadata.gz: 123f788edd6b7510be158be7ffdca00424eea5755aff767ae643180689947636
4
+ data.tar.gz: c789eaf6ecf0ae363faafa8536b11b97e6c6e02af35983ae13f9c8649fe63ab3
5
5
  SHA512:
6
- metadata.gz: d1105627f83fd730a7826047679edfd38ad7deb19960715e93769d5c04e80e0079b02b9513f9e5d01614e2e70b75a016a2e16b7a72f568e2a5d3072c42e7e1dc
7
- data.tar.gz: eb4b14f1f564bd89ae919c16d6c06ef180b725c81b8d78225661b0a0fb34dee067063de9f2b883e6361e51c0db323224b6ee0a80ab3ca80dd9091a0e872ceae4
6
+ metadata.gz: a7af25bbc69dcf5f891900d1620f171451a38ea269cb888ff440061ba0d8f1357a3789bcc446b71beba27a5029ad3214a65197d32165c4ed8e7fcc98fd25ad7e
7
+ data.tar.gz: 89dad0285342e14e9ba1e346a1aae742775048c87bd569b1b8ec5a5edde914435cc3ffb414baa52b948b7af948e5c2cff9ea75cf61503133454eebf780eaebe3
@@ -2,6 +2,35 @@
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
+
13
+ ## fugit 1.3.9 released 2020-09-17
14
+
15
+ * Prevent "New York skip", gh-43, thanks @honglooker
16
+
17
+
18
+ ## fugit 1.3.8 released 2020-08-06
19
+
20
+ * Parse 'every day at 8:30' and ' at 8:30 pm', gh-42
21
+
22
+
23
+ ## fugit 1.3.7 released 2020-08-05
24
+
25
+ * Parse 'every 12 hours at minute 50', gh-41
26
+
27
+
28
+ ## fugit 1.3.6 released 2020-06-01
29
+
30
+ * Introduce new nat syntaxed, gh-38
31
+ * Rework nat parser
32
+
33
+
5
34
  ## fugit 1.3.5 released 2020-05-07
6
35
 
7
36
  * Implement cron @noon, gh-37
data/CREDITS.md CHANGED
@@ -1,6 +1,9 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
+ * Honglooker https://github.com/honglooker gh-43, New York cron skip
5
+ * Jérôme Dalbert https://github.com/jeromedalbert gh-41, gh-42
6
+ * Danny Ben Shitrit https://github.com/DannyBen nat variants, gh-38
4
7
  * Dominik Sander https://github.com/dsander #rough_frequency 0, gh-36
5
8
  * Milovan Zogovic https://github.com/assembler Cron#match? vs TZ, gh-31
6
9
  * Jessica Stokes https://github.com/ticky 0-24 issue with cron, gh-30
data/README.md CHANGED
@@ -9,7 +9,7 @@ Time tools for [flor](https://github.com/floraison/flor) and the floraison group
9
9
 
10
10
  It uses [et-orbi](https://github.com/floraison/et-orbi) to represent time instances and [raabro](https://github.com/floraison/raabro) as a basis for its parsers.
11
11
 
12
- Fugit is a core dependency of [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) 3.5.x.
12
+ Fugit is a core dependency of [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) >= 3.5.
13
13
 
14
14
 
15
15
  ## Related projects
@@ -232,6 +232,29 @@ p d.to_plain_s # => "2Y2M1D5h3600s"
232
232
  p Fugit::Duration.parse('1y2M1d4h').to_sec # => 36820800
233
233
  ```
234
234
 
235
+ There is a `#deflate` method
236
+
237
+ ```ruby
238
+ Fugit::Duration.parse(1000).to_plain_s # => "1000s"
239
+ Fugit::Duration.parse(3600).to_plain_s # => "3600s"
240
+ Fugit::Duration.parse(1000).deflate.to_plain_s # => "16m40s"
241
+ Fugit::Duration.parse(3600).deflate.to_plain_s # => "1h"
242
+
243
+ # or event shorter
244
+ Fugit.parse(1000).deflate.to_plain_s # => "16m40s"
245
+ Fugit.parse(3600).deflate.to_plain_s # => "1h"
246
+ ```
247
+
248
+ There is also an `#inflate` method
249
+
250
+ ```ruby
251
+ Fugit::Duration.parse('1h30m12').inflate.to_plain_s # => "5412s"
252
+ Fugit.parse('1h30m12').inflate.to_plain_s # => "5412s"
253
+
254
+ Fugit.parse('1h30m12').to_sec # => 5412
255
+ Fugit.parse('1h30m12').to_sec.to_s + 's' # => "5412s"
256
+ ```
257
+
235
258
  The `to_*_s` methods are also available as class methods:
236
259
  ```ruby
237
260
  p Fugit::Duration.to_plain_s('1y2M1d4h')
@@ -298,19 +321,35 @@ Fugit.parse('every day at five') # ==> Fugit::Cron instance '0 5 * * *'
298
321
 
299
322
  ### Ambiguous nats
300
323
 
301
- 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.
302
325
 
303
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)
304
330
  Fugit::Nat.parse('every day at 16:00 and 18:00', multi: true)
305
- # ==> [ '0 16,18 * * *' ]
331
+ .collect(&:to_cron_s)
332
+ # ==> [ '0 16,18 * * *' ] (array of Fugit::Cron instances, here only one)
333
+
306
334
  Fugit::Nat.parse('every day at 16:15 and 18:30')
307
- # ==> [ '15 16 * * *' ]
335
+ .to_cron_s
336
+ # ==> '15 16 * * *' (a single of Fugit::Cron instances)
308
337
  Fugit::Nat.parse('every day at 16:15 and 18:30', multi: true)
309
- # ==> [ '15 16 * * *', '30 18 * * *' ]
338
+ .collect(&:to_cron_s)
339
+ # ==> [ '15 16 * * *', '30 18 * * *' ] (two Fugit::Cron instances)
340
+
310
341
  Fugit::Nat.parse('every day at 16:15 and 18:30', multi: :fail)
311
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
312
345
  ```
313
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
+
314
353
 
315
354
  ## LICENSE
316
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.1'
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.5'
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)
@@ -236,6 +237,8 @@ module Fugit
236
237
  # the translation occurs in the timezone of
237
238
  # this Fugit::Cron instance
238
239
 
240
+ zfrom = t.time.strftime('%z|%Z')
241
+
239
242
  loop do
240
243
 
241
244
  fail RuntimeError.new(
@@ -251,8 +254,14 @@ module Fugit
251
254
  min_match?(t) || (t.inc_min; next)
252
255
  sec_match?(t) || (t.inc_sec; next)
253
256
 
254
- st = t.time.strftime('%F|%T')
255
- (from, sfrom, ifrom = t.time, st, t.to_i; next) if st == sfrom
257
+ tt = t.time
258
+ st = tt.strftime('%F|%T')
259
+ zt = tt.strftime('%z|%Z')
260
+ #
261
+ if st == sfrom && zt != zfrom
262
+ from, sfrom, zfrom, ifrom = tt, st, zt, t.to_i
263
+ next
264
+ end
256
265
  #
257
266
  # when transitioning out of DST, this prevents #next_time from
258
267
  # yielding the same literal time twice in a row, see gh-6
@@ -328,7 +337,7 @@ module Fugit
328
337
  [ :seconds, 1, 60 ],
329
338
  [ :minutes, 60, 60 ],
330
339
  [ :hours, 3600, 24 ],
331
- [ :days, 24 * 3600, 365 ] ]
340
+ [ :days, 24 * 3600, 365 ] ].freeze
332
341
 
333
342
  def rough_frequency
334
343
 
@@ -487,7 +496,7 @@ module Fugit
487
496
 
488
497
  sla = 1 if sla == nil
489
498
  sta = min if sta == nil
490
- edn = max if edn == nil
499
+ edn = max if edn == nil || edn < 0 && sta > 0
491
500
 
492
501
  range(min, max, sta, edn, sla)
493
502
  end
@@ -499,12 +508,10 @@ module Fugit
499
508
  { min: min, max: max, sta: sta, edn: edn, sla: sla }.inspect
500
509
  ) if (sta < 0 && edn > 0) || (edn < 0 && sta > 0)
501
510
 
502
- #p({ min: min, max: max, sta: sta, edn: edn, sla: sla })
503
511
  a = []
504
512
 
505
513
  omin, omax = min, max
506
514
  min, max = -max, -1 if sta < 0
507
- #p({ min: min, max: max })
508
515
 
509
516
  cur = sta
510
517
 
@@ -604,10 +611,18 @@ module Fugit
604
611
 
605
612
  module Parser include Raabro
606
613
 
607
- WEEKDAYS = %w[ sunday monday tuesday wednesday thursday friday saturday ]
608
- WEEKDS = WEEKDAYS.collect { |d| d[0, 3] }
614
+ WEEKDAYS =
615
+ %w[ sunday monday tuesday wednesday thursday friday saturday ].freeze
616
+
617
+ WEEKDS =
618
+ WEEKDAYS.collect { |d| d[0, 3] }.freeze
619
+ DOW_REX =
620
+ /([0-7]|#{WEEKDS.join('|')})/i.freeze
609
621
 
610
- MONTHS = %w[ - jan feb mar apr may jun jul aug sep oct nov dec ]
622
+ MONTHS =
623
+ %w[ - jan feb mar apr may jun jul aug sep oct nov dec ].freeze
624
+ MONTH_REX =
625
+ /(1[0-2]|0?[1-9]|#{MONTHS[1..-1].join('|')})/i.freeze
611
626
 
612
627
  # piece parsers bottom to top
613
628
 
@@ -621,8 +636,8 @@ module Fugit
621
636
  def mos(i); rex(:mos, i, /[0-5]?\d/); end # min or sec
622
637
  def hou(i); rex(:hou, i, /(2[0-4]|[01]?[0-9])/); end
623
638
  def dom(i); rex(:dom, i, /(-?(3[01]|[12][0-9]|0?[1-9])|last|l)/i); end
624
- def mon(i); rex(:mon, i, /(1[0-2]|0?[1-9]|#{MONTHS[1..-1].join('|')})/i); end
625
- def dow(i); rex(:dow, i, /([0-7]|#{WEEKDS.join('|')})/i); end
639
+ def mon(i); rex(:mon, i, MONTH_REX); end
640
+ def dow(i); rex(:dow, i, DOW_REX); end
626
641
 
627
642
  def dow_hash(i); rex(:hash, i, /#(-?[1-5]|last|l)/i); end
628
643
 
@@ -757,7 +772,7 @@ module Fugit
757
772
 
758
773
  def rewrite_tz(t)
759
774
 
760
- s = t.string.strip
775
+ s = t.strim
761
776
  z = EtOrbi.get_tzone(s)
762
777
 
763
778
  [ s, z ]
@@ -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,16 +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
- a = Parser.parse(s)
20
20
 
21
- return nil unless a
22
-
23
- return parse_crons(s, a, opts) \
24
- if a.include?([ :flag, 'every' ])
25
- return parse_crons(s, a, opts) \
26
- if a.include?([ :flag, 'from' ]) && a.find { |e| e[0] == :day_range }
27
-
28
- nil
21
+ if slots = Parser.parse(s)
22
+ slots.to_crons(opts.merge(_s: s))
23
+ else
24
+ nil
25
+ end
29
26
  end
30
27
 
31
28
  def do_parse(s, opts={})
@@ -33,327 +30,653 @@ module Fugit
33
30
  parse(s, opts) ||
34
31
  fail(ArgumentError.new("could not parse a nat #{s.inspect}"))
35
32
  end
33
+ end
36
34
 
37
- protected
35
+ module Parser include Raabro
38
36
 
39
- 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
40
61
 
41
- hms, aa = a
42
- .partition { |e| e[0] == :point && e[1][0] == :hour }
43
- ms = hms
44
- .inject({}) { |h, e| (h[e[1][1]] ||= []) << e[1][2]; h }
45
- .values
46
- .uniq
47
- crons =
48
- ms.size > 1 ?
49
- hms.collect { |e| parse_cron([ e ] + aa, opts) } :
50
- [ parse_cron(a, opts) ]
62
+ WEEKDAYS = (
63
+ Fugit::Cron::Parser::WEEKDAYS +
64
+ Fugit::Cron::Parser::WEEKDS).freeze
51
65
 
52
- fail ArgumentError.new(
53
- "multiple crons in #{s.inspect} " +
54
- "(#{crons.collect(&:original).join(' | ')})"
55
- ) if opts[:multi] == :fail && crons.size > 1
66
+ POINTS = %w[
67
+ minutes? mins? seconds? secs? hours? hou h ].freeze
56
68
 
57
- if opts[:multi] == true || (opts[:multi] && opts[:multi] != :fail)
58
- crons
59
- else
60
- crons.first
61
- end
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)
62
133
  end
63
134
 
64
- def parse_cron(a, opts)
65
-
66
- h = { min: nil, hou: nil, dom: nil, mon: nil, dow: nil }
67
- hkeys = h.keys
68
-
69
- i0s, es = a.partition { |e| e[0] == :interval0 }
70
- a = es + i0s
71
- # interval0s are fallback
72
-
73
- a.each do |key, val|
74
- case key
75
- when :biz_day
76
- (h[:dow] ||= []) << '1-5'
77
- when :name_day
78
- (h[:dow] ||= []) << val
79
- when :day_range
80
- (h[:dow] ||= []) << val.collect { |v| v.to_s[0, 3] }.join('-')
81
- when :tz
82
- h[:tz] = val
83
- when :point
84
- process_point(h, *val)
85
- when :interval1
86
- process_interval1(h, *val[0].to_h.first)
87
- when :interval0
88
- process_interval0(h, val)
89
- end
90
- end
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
137
+
138
+ def weekdays(i); jseq(:weekdays, i, :weekday, :_and_or_or_or_comma); end
91
139
 
92
- return nil if h[:fail]
140
+ def on_the(i); seq(nil, i, :_the, :omonthdays); end
93
141
 
94
- h[:min] = (h[:min] || [ 0 ]).uniq
95
- h[:hou] = (h[:hou] || []).uniq.sort
96
- h[:dow].sort! if h[:dow]
142
+ def _minute(i); rex(nil, i, /[ \t]*minute[ \t]+/i) end
97
143
 
98
- a = hkeys
99
- .collect { |k|
100
- v = h[k]
101
- (v && v.any?) ? v.collect(&:to_s).join(',') : '*' }
102
- a.insert(0, h[:sec]) if h[:sec]
103
- a << h[:tz].first if h[:tz]
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)
149
+ end
104
150
 
105
- s = a.join(' ')
151
+ def on_minutes(i)
152
+ seq(:on_minutes, i, :_minute, :_dmin, :and_dmin, '*')
153
+ end
106
154
 
107
- Fugit::Cron.parse(s)
155
+ def on_thex(i);
156
+ rex(:on_thex, i, /[ \t]*the[ \t]+(hour|minute)[ \t]*/i);
108
157
  end
109
158
 
110
- def process_point(h, key, *value)
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
111
162
 
112
- case key
113
- when :hour
114
- v0, v1 = value
115
- v0 = v0.to_i if v0.is_a?(String) && v0.match(/^\d+$/)
116
- (h[:hou] ||= []) << v0
117
- (h[:min] ||= []) << v1.to_i if v1
118
- when :sec, :min
119
- (h[key] ||= []) << value[0]
120
- end
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)
121
168
  end
122
169
 
123
- def process_interval0(h, value)
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
124
180
 
125
- case value
126
- when 'sec', 'second'
127
- h[:min] = [ '*' ]
128
- h[:sec] = [ '*' ]
129
- when 'min', 'minute'
130
- h[:min] = [ '*' ]
131
- when 'hour'
132
- unless h[:min] || h[:hou]
133
- h[:min] = [ 0 ]
134
- h[:hou] = [ '*' ]
135
- end
136
- when 'day'
137
- unless h[:min] || h[:hou]
138
- h[:min] = [ 0 ]
139
- h[:hou] = [ 0 ]
140
- end
141
- when 'week'
142
- unless h[:min] || h[:hou] || h[:dow]
143
- h[:min] = [ 0 ]
144
- h[:hou] = [ 0 ]
145
- h[:dow] = [ 0 ]
146
- end
147
- when 'month'
148
- unless h[:min] || h[:hou]
149
- h[:min] = [ 0 ]
150
- h[:hou] = [ 0 ]
151
- end
152
- (h[:dom] ||= []) << 1
153
- when 'year'
154
- unless h[:min] || h[:hou]
155
- h[:min] = [ 0 ]
156
- h[:hou] = [ 0 ]
157
- end
158
- (h[:dom] ||= []) << 1
159
- (h[:mon] ||= []) << 1
160
- end
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)
161
195
  end
162
196
 
163
- def process_interval1(h, interval, value)
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
164
202
 
165
- if value != 1 && [ :yea, :wee ].include?(interval)
166
- int = interval == :year ? 'years' : 'weeks'
167
- h[:fail] = "cannot cron for \"every #{value} #{int}\""
168
- return
169
- end
203
+ def ampm(i)
204
+ rex(:ampm, i, /[ \t]*(am|pm)/i)
205
+ end
206
+ def dark(i)
207
+ rex(:dark, i, /[ \t]*dark/i)
208
+ end
170
209
 
171
- case interval
172
- when :yea
173
- h[:hou] = [ 0 ]
174
- h[:mon] = [ 1 ]
175
- h[:dom] = [ 1 ]
176
- when :mon
177
- h[:hou] = [ 0 ]
178
- h[:dom] = [ 1 ]
179
- h[:mon] = [ value == 1 ? '*' : "*/#{value}" ]
180
- when :wee
181
- h[:hou] = [ 0 ]
182
- h[:dow] = [ 0 ] # Sunday
183
- when :day
184
- h[:hou] = [ 0 ]
185
- h[:dom] = [ value == 1 ? '*' : "*/#{value}" ]
186
- when :hou
187
- h[:hou] = [ value == 1 ? '*' : "*/#{value}" ]
188
- when :min
189
- h[:hou] = [ '*' ]
190
- h[:min] = [ value == 1 ? '*' : "*/#{value}" ]
191
- when :sec
192
- h[:hou] = [ '*' ]
193
- h[:min] = [ '*' ]
194
- h[:sec] = [ value == 1 ? '*' : "*/#{value}" ]
195
- end
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, '?')
196
215
  end
197
- end
198
216
 
199
- module Parser include Raabro
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
200
223
 
201
- NUMS = %w[
202
- zero
203
- one two three four five six seven eight nine
204
- ten eleven twelve ]
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, '?')
229
+ end
205
230
 
206
- WEEKDAYS =
207
- Fugit::Cron::Parser::WEEKDS + Fugit::Cron::Parser::WEEKDAYS
231
+ def _point(i); rex(:point, i, POINT_REX); end
208
232
 
209
- NHOURS =
210
- { 'noon' => [ 12, 0 ], 'midnight' => [ 0, 0 ] }
233
+ def counts(i)
234
+ jseq(nil, i, :count, :_and_or_or_or_comma)
235
+ end
236
+
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
211
243
 
212
- # piece parsers bottom to top
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)
255
+ end
213
256
 
214
- def interval0(i)
215
- rex(:interval0, i,
216
- /(year|month|week|day|hour|min(ute)?|sec(ond)?)(?![a-z])/i)
257
+ def at(i)
258
+ seq(:at, i, :_at, '?', :at_objects)
217
259
  end
218
260
 
219
- def am_pm(i)
220
- rex(:am_pm, i, / *(am|pm)/i)
261
+ def interval(i)
262
+ rex(:interval, i, INTERVAL_REX)
221
263
  end
222
264
 
223
- def digital_hour(i)
224
- rex(:digital_hour, i, /(2[0-4]|[01][0-9]):?[0-5]\d/)
265
+ # every day
266
+ # every 1 minute
267
+ def every_interval(i)
268
+ seq(:every_interval, i, :count, '?', :interval)
225
269
  end
226
270
 
227
- def _simple_hour(i)
228
- rex(:sh, i, /(2[0-4]|[01]?[0-9])/)
271
+ def every_single_interval(i)
272
+ rex(:every_single_interval, i, /(1[ \t]+)?(week|year)/)
229
273
  end
230
- def simple_hour(i)
231
- seq(:simple_hour, i, :_simple_hour, :am_pm, '?')
274
+
275
+ def to_weekday(i)
276
+ seq(:to_weekday, i, :weekday, :_to_or_dash, :weekday)
232
277
  end
233
278
 
234
- def _numeral_hour(i)
235
- rex(:nh, i, /(#{NUMS.join('|')})/i)
279
+ def weekday_range(i)
280
+ alt(nil, i, :to_weekday, :weekdays)
236
281
  end
237
- def numeral_hour(i)
238
- seq(:numeral_hour, i, :_numeral_hour, :am_pm, '?')
282
+
283
+ def to_omonthday(i)
284
+ seq(:to_omonthday, i,
285
+ :_the, '?', :omonthday, :_to, :_the, '?', :omonthday)
239
286
  end
240
287
 
241
- def name_hour(i)
242
- rex(:name_hour, i, /(#{NHOURS.keys.join('|')})/i)
288
+ def to_hour(i)
289
+ seq(:to_hour, i, :at_object, :_to, :at_object)
243
290
  end
244
291
 
245
- def biz_day(i); rex(:biz_day, i, /(biz|business|week) *day/i); end
246
- def name_day(i); rex(:name_day, i, /#{WEEKDAYS.reverse.join('|')}/i); end
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
247
301
 
248
- def range_sep(i); rex(nil, i, / *- *| +(to|through) +/); end
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
249
308
 
250
- def day_range(i)
251
- seq(:day_range, i, :name_day, :range_sep, :name_day)
309
+ def otm(i)
310
+ rex(nil, i, /[ \t]+of the month/)
252
311
  end
253
312
 
254
- def _tz_name(i)
255
- rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/)
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)
256
320
  end
257
- def _tz_delta(i)
258
- rex(nil, i, /[-+]([01][0-9]|2[0-4]):?(00|15|30|45)/)
321
+
322
+ def every_named(i)
323
+ rex(:every_named, i, /weekday/i)
259
324
  end
260
- def _tz(i); alt(:tz, i, :_tz_delta, :_tz_name); end
261
325
 
262
- def interval1(i)
263
- rex(:interval1, i,
264
- /
265
- \d+
266
- \s?
267
- (y(ears?)?|months?|w(eeks?)?|d(ays?)?|
268
- h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?)
269
- /ix)
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)
331
+ end
332
+ def every_objects(i)
333
+ jseq(nil, i, :every_object, :_and_or_or)
270
334
  end
271
335
 
272
- def min_or_sec(i)
273
- rex(:min_or_sec, i, /(min(ute)?|sec(ond)?)\s+\d+/i)
336
+ def every(i)
337
+ seq(:every, i, :_every, :every_objects)
274
338
  end
275
339
 
276
- def point(i)
277
- alt(:point, i,
278
- :min_or_sec,
279
- :name_hour, :numeral_hour, :digital_hour, :simple_hour)
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)
280
345
  end
281
346
 
282
- def flag(i); rex(:flag, i, /(every|from|at|after|on|in)/i); end
347
+ #
348
+ # rewrite parsed tree ###################################################
283
349
 
284
- def datum(i)
285
- alt(nil, i,
286
- :flag,
287
- :interval1,
288
- :point,
289
- :interval0,
290
- :day_range, :biz_day, :name_day,
291
- :_tz)
350
+ def slot(key, data0, data1=nil, opts=nil)
351
+ Slot.new(key, data0, data1, opts)
292
352
  end
293
353
 
294
- def sugar(i); rex(nil, i, /(and|or|[, \t]+)/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
295
361
 
296
- def elt(i); alt(nil, i, :sugar, :datum); end
297
- def nat(i); rep(:nat, i, :elt, 1); end
362
+ def rewrite_dmin(t)
363
+ t.strinp
364
+ end
298
365
 
299
- # rewrite parsed tree
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
300
372
 
301
- def _rewrite(t)
302
- [ t.name, t.string.downcase ]
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
303
380
  end
304
- alias rewrite_flag _rewrite
305
- alias rewrite_interval0 _rewrite
306
- alias rewrite_biz_day _rewrite
307
381
 
308
- def rewrite_name_day(t)
309
- [ :name_day, WEEKDAYS.index(t.string.downcase[0, 3]) ]
382
+ def rewrite_on_thes(t)
383
+ _rewrite_subs(t, :omonthday)
384
+ end
385
+ def rewrite_on_days(t)
386
+ _rewrite_subs(t, :monthday)
310
387
  end
311
388
 
312
- def rewrite_day_range(t)
313
- [ :day_range, t.subgather(nil).collect { |st| st.string.downcase } ]
389
+ def rewrite_on(t)
390
+ _rewrite_subs(t)
314
391
  end
315
392
 
316
- def rewrite_name_hour(t)
317
- [ :hour, *NHOURS[t.string.strip.downcase] ]
393
+ def rewrite_monthday(t)
394
+ slot(:monthday, t.string.to_i)
318
395
  end
319
- def rewrite_numeral_hour(t)
320
- vs = t.subgather(nil).collect { |st| st.string.downcase.strip }
321
- v = NUMS.index(vs[0])
322
- v += 12 if vs[1] == 'pm'
323
- [ :hour, v, 0 ]
396
+
397
+ def rewrite_omonthday(t)
398
+ slot(:monthday, OMONTHDAYS[t.string.downcase])
324
399
  end
325
- def rewrite_simple_hour(t)
326
- vs = t.subgather(nil).collect { |st| st.string.downcase.strip }
327
- v = vs[0].to_i
328
- v += 12 if vs[1] == 'pm'
329
- [ :hour, v, 0 ]
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
330
412
  end
331
- def rewrite_digital_hour(t)
332
- v = t.string.gsub(/:/, '')
333
- [ :hour, v[0, 2], v[2, 2] ]
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
334
420
  end
335
421
 
336
- def rewrite_min_or_sec(t)
337
- unit, num = t.string.split(/\s+/)
338
- [ unit[0, 3].to_sym, num.to_i ]
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
339
440
  end
340
441
 
341
- def rewrite_point(t)
342
- [ :point, rewrite(t.sublookup) ]
442
+ def rewrite_every_named(t)
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
343
449
  end
344
450
 
345
451
  def rewrite_tz(t)
346
- [ :tz, [ t.string.strip, EtOrbi.get_tzone(t.string.strip) ] ]
452
+ slot(:tz, t.string)
347
453
  end
348
454
 
349
- def rewrite_interval1(t)
350
- [ t.name, [ Fugit::Duration.parse(t.string.strip) ] ]
455
+ def rewrite_weekday(t)
456
+ Fugit::Cron::Parser::WEEKDS.index(t.string[0, 3].downcase)
351
457
  end
352
458
 
353
- def rewrite_nat(t)
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
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 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
482
+
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)
488
+ end
489
+
490
+ def rewrite_named_hour(t)
491
+
492
+ ht = t.sublookup(:named_h)
493
+ mt = t.sublookup(:named_m)
494
+ apt = t.sublookup(:ampm)
495
+
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 ]
502
+
503
+ h += 12 if h < 13 && apt && apt.strinpd == 'pm'
504
+
505
+ slot(:hm, h, m)
506
+ end
507
+
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)
516
+ end
517
+
518
+ def rewrite_at(t)
519
+ _rewrite_subs(t)
520
+ end
521
+
522
+ def rewrite_every(t)
523
+ _rewrite_sub(t)
524
+ end
354
525
 
526
+ def rewrite_nat(t)
355
527
  #Raabro.pp(t, colours: true)
356
- t.subgather(nil).collect { |tt| rewrite(tt) }
528
+ Fugit::Nat::SlotGroup.new(_rewrite_subs(t).flatten)
529
+ end
530
+ end
531
+
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
581
+ end
582
+ end
583
+
584
+ class SlotGroup
585
+
586
+ def initialize(slots)
587
+
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
609
+ end
610
+
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
626
+
627
+ protected
628
+
629
+ def make_slot(key, data0, data1=nil)
630
+
631
+ Fugit::Nat::Slot.new(key, data0, data1)
632
+ end
633
+
634
+ def determine_hms
635
+
636
+ return [ [ [ '*' ], [ '*' ] ] ] if @hms.empty?
637
+
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
676
+
677
+ def slot(key, default)
678
+ s = @slots[key]
679
+ s ? s.data0 : [ default ]
357
680
  end
358
681
  end
359
682
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fugit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.5
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Mettraux
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-07 00:00:00.000000000 Z
11
+ date: 2020-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raabro
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.1'
19
+ version: '1.4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.1'
26
+ version: '1.4'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: et-orbi
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -93,15 +93,15 @@ files:
93
93
  - lib/fugit/misc.rb
94
94
  - lib/fugit/nat.rb
95
95
  - lib/fugit/parse.rb
96
- homepage: http://github.com/floraison/fugit
96
+ homepage: https://github.com/floraison/fugit
97
97
  licenses:
98
98
  - MIT
99
99
  metadata:
100
- changelog_uri: http://github.com/floraison/fugit/blob/master/CHANGELOG.md
101
- documentation_uri: http://github.com/floraison/fugit
102
- bug_tracker_uri: http://github.com/floraison/fugit/issues
103
- homepage_uri: http://github.com/floraison/fugit
104
- source_code_uri: http://github.com/floraison/fugit
100
+ changelog_uri: https://github.com/floraison/fugit/blob/master/CHANGELOG.md
101
+ documentation_uri: https://github.com/floraison/fugit
102
+ bug_tracker_uri: https://github.com/floraison/fugit/issues
103
+ homepage_uri: https://github.com/floraison/fugit
104
+ source_code_uri: https://github.com/floraison/fugit
105
105
  post_install_message:
106
106
  rdoc_options: []
107
107
  require_paths: