fugit 1.3.1 → 1.3.6

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
- SHA1:
3
- metadata.gz: b6c020a2dc2dea8ee73eae196c502e1353839f84
4
- data.tar.gz: d4d43e932e0e4de4d07d402db975bf2a642df687
2
+ SHA256:
3
+ metadata.gz: 90b61604e15e2cb24d4fb124306affea15a644c2591fc3a49649981616351d66
4
+ data.tar.gz: 235eec423691346077fd9cfdaed895e28843ecf6681b5aa00a83cfcf9ca711a3
5
5
  SHA512:
6
- metadata.gz: 7c7f5319195ca991c848826b336023f429fc21ad033b26d67b7011872b91d1e9f8fb639b2618eda9dd1e199d7e473599e356924f302bb5cbaba20817aa6ab050
7
- data.tar.gz: 4358df79feebbdb50509b78735fad8210d5c3d001d6a0b3d5a85aa375be08605816ea87510abc8188b5e42d091cd4d47af91a18061d956dc10a115fd9397c7eb
6
+ metadata.gz: 1fd5abf6871b1cb60220bf7d6089be6bff48dfae80a05db6d031def126275940d50cbc5dfcaad493f2b6d327b66162c2a44329966459bfb3d9d5b912e3b7e68c
7
+ data.tar.gz: '08a846fcce60834f8f0c289e14317249123f1f4028c478953ed04f759e633080c4d843f55c56fb973a104e1b44bbbbc92eecc978a954d7b0b317d7b23b82611b'
@@ -2,6 +2,33 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.3.6 released 2020-06-01
6
+
7
+ * Introduce new nat syntaxed, gh-38
8
+ * Rework nat parser
9
+
10
+
11
+ ## fugit 1.3.5 released 2020-05-07
12
+
13
+ * Implement cron @noon, gh-37
14
+ * Normalize "every x", gh-37
15
+
16
+
17
+ ## fugit 1.3.4 released 2020-04-06
18
+
19
+ * Prevent #rough_frequency returning 0, gh-36
20
+
21
+
22
+ ## fugit 1.3.3 released 2019-08-29
23
+
24
+ * Fix Cron#match?(t) with respect to the cron's timezone, gh-31
25
+
26
+
27
+ ## fugit 1.3.2 released 2019-08-14
28
+
29
+ * Allow for "* 0-24 * * *", gh-30
30
+
31
+
5
32
  ## fugit 1.3.1 released 2019-07-27
6
33
 
7
34
  * Fix nat parsing for 'every day at 18:00 and 18:15', gh-29
data/CREDITS.md CHANGED
@@ -1,7 +1,11 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
- * Shai Coleman https://github.com/shaicoleman parse_nat enhancements, gh-24, gh-25, and gh-28
4
+ * Danny Ben Shitrit https://github.com/DannyBen nat variants, gh-38
5
+ * Dominik Sander https://github.com/dsander #rough_frequency 0, gh-36
6
+ * Milovan Zogovic https://github.com/assembler Cron#match? vs TZ, gh-31
7
+ * Jessica Stokes https://github.com/ticky 0-24 issue with cron, gh-30
8
+ * Shai Coleman https://github.com/shaicoleman parse_nat enhancements, gh-24, gh-25, gh-28, and gh-37
5
9
  * Jan Stevens https://github.com/JanStevens Fugit.parse('every 15 minutes') gh-22
6
10
  * Fabio Pitino https://github.com/hspazio nil on February 30 gh-21
7
11
  * Cristian Oneț https://github.com/conet #previous_time vs 1/-1 endless loop gh-15
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2017-2019, John Mettraux, jmettraux+flor@gmail.com
2
+ Copyright (c) 2017-2020, 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/Makefile CHANGED
@@ -31,7 +31,7 @@ build: gemspec_validate
31
31
  mv $(NAME)-$(VERSION).gem pkg/
32
32
 
33
33
  push: build
34
- gem push pkg/$(NAME)-$(VERSION).gem
34
+ gem push --otp "$(OTP)" pkg/$(NAME)-$(VERSION).gem
35
35
 
36
36
  spec:
37
37
  bundle exec rspec
data/README.md CHANGED
@@ -36,6 +36,7 @@ The intersection of those two projects is where fugit is born:
36
36
  * [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) -
37
37
  * [flor](https://github.com/floraison/flor) - used in the [cron](https://github.com/floraison/flor/blob/master/doc/procedures/cron.md) procedure
38
38
  * [que-scheduler](https://github.com/hlascelles/que-scheduler) - a reliable job scheduler for [que](https://github.com/chanks/que)
39
+ * [serial_scheduler](https://github.com/grosser/serial_scheduler) - ruby task scheduler without threading
39
40
  * ...
40
41
 
41
42
  ## `Fugit.parse(s)`
@@ -63,7 +64,7 @@ Fugit.parse('nada')
63
64
  ## `Fugit.do_parse(s)`
64
65
 
65
66
  `Fugit.do_parse(s)` is equivalent to `Fugit.parse(s)`, but instead of returning nil, it raises an error if the given string contains no time information.
66
- ```
67
+ ```ruby
67
68
  Fugit.do_parse('nada')
68
69
  # ==> /home/jmettraux/w/fugit/lib/fugit/parse.rb:32
69
70
  # :in `do_parse': found no time information in "nada" (ArgumentError)
@@ -137,6 +138,49 @@ Example of cron strings understood by fugit:
137
138
  # and more...
138
139
  ```
139
140
 
141
+ ### the first Monday of the month
142
+
143
+ Fugit tries to follow the `man 5 crontab` documentation.
144
+
145
+ There is a surprising thing about this canon, all the columns are joined by ANDs, except for monthday and weekday which are joined together by OR if they are both set (they are not `*`).
146
+
147
+ Many people (me included) [are suprised](https://superuser.com/questions/428807/run-a-cron-job-on-the-first-monday-of-every-month) when they try to specify "at 05:00 on the first Monday of the month" as `0 5 1-7 * 1` or `0 5 1-7 * mon` and the results are off.
148
+
149
+ The man page says:
150
+
151
+ > Note: The day of a command's execution can be specified by
152
+ > two fields -- day of month, and day of week. If both fields
153
+ > are restricted (ie, are not *), the command will be run when
154
+ > either field matches the current time.
155
+ > For example, ``30 4 1,15 * 5'' would cause a command to be run
156
+ > at 4:30 am on the 1st and 15th of each month, plus every Friday.
157
+
158
+ Fugit follows this specification.
159
+
160
+ There is a solution though, please read on.
161
+
162
+ ### the hash extension
163
+
164
+ Fugit understands `0 5 * * 1#1` or `0 5 * * mon#1` as "each first Monday of the month, at 05:00".
165
+
166
+ ```ruby
167
+ '0 5 * * 1#1' #
168
+ '0 5 * * mon#1' # the first Monday of the month at 05:00
169
+
170
+ '0 6 * * 5#4,5#5' #
171
+ '0 6 * * fri#4,fri#5' # the 4th and 5th Fridays of the month at 06:00
172
+
173
+ '0 7 * * 5#-1' #
174
+ '0 7 * * fri#-1' # the last Friday of the month at 07:00
175
+
176
+ '0 7 * * 5#L' #
177
+ '0 7 * * fri#L' #
178
+ '0 7 * * 5#last' #
179
+ '0 7 * * fri#last' # the last Friday of the month at 07:00
180
+
181
+ '0 23 * * mon#2,tue' # the 2nd Monday of the month and every Tuesday, at 23:00
182
+ ```
183
+
140
184
  ### the modulo extension
141
185
 
142
186
  Fugit, since 1.1.10, also understands cron strings like "`9 0 * * sun%2`" which can be read as "every other Sunday at 9am".
@@ -145,7 +189,7 @@ For odd Sundays, one can write `9 0 * * sun%2+1`.
145
189
 
146
190
  It can be combined, as in `9 0 * * sun%2,tue%3+2`
147
191
 
148
- But what does it references to? It starts at 1 on 2019-01-01.
192
+ But what does it reference to? It starts at 1 on 2019-01-01.
149
193
 
150
194
  ```ruby
151
195
  require 'et-orbi' # >= 1.1.8
@@ -184,10 +228,35 @@ p d.to_long_s # => "2 years, 2 months, 1 day, and 5 hours"
184
228
  d += 3600
185
229
 
186
230
  p d.to_plain_s # => "2Y2M1D5h3600s"
231
+
232
+ p Fugit::Duration.parse('1y2M1d4h').to_sec # => 36820800
187
233
  ```
188
234
 
189
- The `to_*_s` methods are also available as class methods:
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"
190
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
+
258
+ The `to_*_s` methods are also available as class methods:
259
+ ```ruby
191
260
  p Fugit::Duration.to_plain_s('1y2M1d4h')
192
261
  # => "1Y2M1D4h"
193
262
  p Fugit::Duration.to_iso_s('1y2M1d4h')
@@ -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.3'
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,7 @@
1
1
 
2
2
  module Fugit
3
3
 
4
- VERSION = '1.3.1'
4
+ VERSION = '1.3.6'
5
5
  end
6
6
 
7
7
  require 'time'
@@ -11,6 +11,7 @@ module Fugit
11
11
  '@weekly' => '0 0 * * 0',
12
12
  '@daily' => '0 0 * * *',
13
13
  '@midnight' => '0 0 * * *',
14
+ '@noon' => '0 12 * * *',
14
15
  '@hourly' => '0 * * * *' }
15
16
  MAXDAYS = [
16
17
  nil, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
@@ -188,6 +189,17 @@ module Fugit
188
189
 
189
190
  return weekday_match?(nt) || monthday_match?(nt) \
190
191
  if @weekdays && @monthdays
192
+ #
193
+ # From `man 5 crontab`
194
+ #
195
+ # Note: The day of a command's execution can be specified
196
+ # by two fields -- day of month, and day of week.
197
+ # If both fields are restricted (ie, are not *), the command will be
198
+ # run when either field matches the current time.
199
+ # For example, ``30 4 1,15 * 5'' would cause a command to be run
200
+ # at 4:30 am on the 1st and 15th of each month, plus every Friday.
201
+ #
202
+ # as seen in gh-5 and gh-35
191
203
 
192
204
  return false unless weekday_match?(nt)
193
205
  return false unless monthday_match?(nt)
@@ -197,7 +209,7 @@ module Fugit
197
209
 
198
210
  def match?(t)
199
211
 
200
- t = Fugit.do_parse_at(t)
212
+ t = Fugit.do_parse_at(t).translate(@timezone)
201
213
 
202
214
  month_match?(t) && day_match?(t) &&
203
215
  hour_match?(t) && min_match?(t) && sec_match?(t)
@@ -336,6 +348,7 @@ module Fugit
336
348
  return (a + [ a.first + v1 ])
337
349
  .each_cons(2)
338
350
  .collect { |a0, a1| a1 - a0 }
351
+ .select { |d| d > 0 } # weed out zero deltas
339
352
  .min * v0
340
353
  end
341
354
 
@@ -497,23 +510,28 @@ module Fugit
497
510
 
498
511
  loop do
499
512
 
500
- #p({ cur: cur })
501
513
  a << cur
502
514
  break if cur == edn
503
515
 
504
516
  cur += 1
505
- cur = min if cur > max
506
- #p cur
517
+ if cur > max
518
+ cur = min
519
+ edn = edn - max - 1 if edn > max
520
+ end
507
521
 
508
522
  fail RuntimeError.new(
509
523
  "too many loops for " +
510
524
  { min: omin, max: omax, sta: sta, edn: edn, sla: sla }.inspect +
511
525
  " #range, breaking, " +
512
526
  "please fill an issue at https://git.io/fjJC9"
513
- ) if a.length > omax
527
+ ) if a.length > 2 * omax
528
+ # there is a #uniq afterwards, hence the 2* for 0-24 and friends
514
529
  end
515
530
 
516
- a.each_with_index.select { |e, i| i % sla == 0 }.collect(&:first)
531
+ a.each_with_index
532
+ .select { |e, i| i % sla == 0 }
533
+ .collect(&:first)
534
+ .uniq
517
535
  end
518
536
 
519
537
  def compact(key)
@@ -739,7 +757,7 @@ module Fugit
739
757
 
740
758
  def rewrite_tz(t)
741
759
 
742
- s = t.string.strip
760
+ s = t.strim
743
761
  z = EtOrbi.get_tzone(s)
744
762
 
745
763
  [ s, z ]
@@ -15,16 +15,8 @@ module Fugit
15
15
  return nil unless s.is_a?(String)
16
16
 
17
17
  #p s; Raabro.pp(Parser.parse(s, debug: 3), colours: true)
18
- a = Parser.parse(s)
19
-
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
18
+ #(p s; Raabro.pp(Parser.parse(s, debug: 1), colours: true)) rescue nil
19
+ parse_crons(s, Parser.parse(s), opts)
28
20
  end
29
21
 
30
22
  def do_parse(s, opts={})
@@ -37,20 +29,37 @@ module Fugit
37
29
 
38
30
  def parse_crons(s, a, opts)
39
31
 
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
32
+ #p a
33
+ return nil unless a
34
+
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
43
+
44
+ if f = h[:_fail]
45
+ #fail ArgumentError.new(f)
46
+ return nil
47
+ end
48
+
49
+ hms = h[:hms]
50
+
51
+ hours = (hms || [])
45
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
46
60
 
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
61
+ crons = hours
62
+ .collect { |hm| assemble_cron(h.merge(hms: hm)) }
54
63
 
55
64
  fail ArgumentError.new(
56
65
  "multiple crons in #{s.inspect} " +
@@ -64,207 +73,451 @@ module Fugit
64
73
  end
65
74
  end
66
75
 
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
90
-
91
- h[:min] ||= [ 0 ]
92
- h[:min].uniq!
76
+ def assemble_cron(h)
93
77
 
94
- h[:hou].uniq!;
95
- h[:hou].sort!
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]
96
85
 
97
- h[:dow].sort! if h[:dow]
98
-
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]
105
-
106
- s = a.join(' ')
86
+ Fugit::Cron.parse(s.join(' '))
87
+ end
107
88
 
108
- Fugit::Cron.parse(s)
89
+ def eone(e); e1 = e[1]; e1 == 1 ? '*' : "*/#{e1}"; end
90
+
91
+ def parse_interval_elt(e, opts, h)
92
+
93
+ e1 = e[1]
94
+
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
+ h[:hms] ||= [ [ '*', eone(e) ] ]
101
+ when 'h', 'hour', 'hours'
102
+ h[:hms] ||= [ [ eone(e), 0 ] ]
103
+ when 'd', 'day', 'days'
104
+ h[:dom] = "*/#{e1}" if e1 > 1
105
+ h[:hms] ||= [ [ 0, 0 ] ]
106
+ when 'w', 'week', 'weeks'
107
+ h[:_fail] = "cannot have crons for \"every #{e1} weeks\"" if e1 > 1
108
+ h[:hms] ||= [ [ 0, 0 ] ]
109
+ h[:dow] ||= 0
110
+ when 'M', 'month', 'months'
111
+ h[:_fail] = "cannot have crons for \"every #{e1} months\"" if e1 > 12
112
+ h[:hms] ||= [ [ 0, 0 ] ]
113
+ h[:dom] = 1
114
+ h[:mon] = eone(e)
115
+ when 'Y', 'y', 'year', 'years'
116
+ h[:_fail] = "cannot have crons for \"every #{e1} years\"" if e1 > 1
117
+ h[:hms] ||= [ [ 0, 0 ] ]
118
+ h[:dom] = 1
119
+ h[:mon] = 1
120
+ end
109
121
  end
110
122
 
111
- def process_duration(h, interval, value)
123
+ def parse_dow_list_elt(e, opts, h)
112
124
 
113
- send("process_duration_#{interval}", h, value)
125
+ h[:hms] ||= [ [ 0, 0 ] ]
126
+ h[:dow] = e[1..-1].collect(&:to_s).sort.join(',')
114
127
  end
115
128
 
116
- def process_duration_mon(h, value)
129
+ def parse_dow_range_elt(e, opts, h)
117
130
 
118
- h[:hou] = [ 0 ]
119
- h[:dom] = [ 1 ]
120
- h[:mon] = [ value == 1 ? '*' : "*/#{value}" ]
131
+ h[:hms] ||= [ [ 0, 0 ] ]
132
+ h[:dow] = e[1] == e[2] ? e[1] : "#{e[1]}-#{e[2]}"
121
133
  end
122
134
 
123
- def process_duration_day(h, value)
135
+ def parse_day_of_month_elt(e, opts, h)
124
136
 
125
- h[:hou] = [ 0 ]
126
- h[:dom] = [ value == 1 ? '*' : "*/#{value}" ]
137
+ h[:dom] = e[1..-1].join(',')
127
138
  end
128
139
 
129
- def process_duration_hou(h, value)
140
+ def parse_at_elt(e, opts, h)
130
141
 
131
- h[:hou] = [ value == 1 ? '*' : "*/#{value}" ]
142
+ (h[:hms] ||= []).concat(e[1])
143
+
144
+ l = h[:hms].last
145
+ h[:sec] = l.pop if l.size > 2
132
146
  end
133
147
 
134
- def process_duration_min(h, value)
148
+ def parse_on_elt(e, opts, h)
149
+
150
+ e1 = e[1]
151
+ h[:dow] = e1[0]
152
+ h[:hms] = [ e1[1] ]
135
153
 
136
- h[:hou] = [ '*' ]
137
- h[:min] = [ value == 1 ? '*' : "*/#{value}" ]
154
+ l = h[:hms].last
155
+ h[:sec] = l.pop if l.size > 2
138
156
  end
139
157
 
140
- def process_duration_sec(h, value)
158
+ def parse_tz_elt(e, opts, h)
141
159
 
142
- h[:hou] = [ '*' ]
143
- h[:min] = [ '*' ]
144
- h[:sec] = [ value == 1 ? '*' : "*/#{value}" ]
160
+ h[:tz] = e[1]
145
161
  end
146
162
  end
147
163
 
148
164
  module Parser include Raabro
149
165
 
150
166
  NUMS = %w[
151
- zero
152
- one two three four five six seven eight nine
153
- ten eleven twelve ]
167
+ zero one two three four five six seven eight nine ten eleven twelve ]
154
168
 
155
169
  WEEKDAYS =
156
170
  Fugit::Cron::Parser::WEEKDS + Fugit::Cron::Parser::WEEKDAYS
157
171
 
158
- NHOURS =
159
- { 'noon' => [ 12, 0 ], 'midnight' => [ 0, 0 ] }
172
+ NHOURS = {
173
+ 'noon' => [ 12, 0 ],
174
+ 'midnight' => [ 0, 0 ], 'oh' => [ 0, 0 ] }
175
+ NMINUTES = {
176
+ "o'clock" => 0, 'five' => 5,
177
+ 'ten' => 10, 'fifteen' => 15,
178
+ 'twenty' => 20, 'twenty-five' => 25,
179
+ 'thirty' => 30, 'thirty-five' => 35,
180
+ 'fourty' => 40, 'fourty-five' => 45,
181
+ 'fifty' => 50, 'fifty-five' => 55 }
182
+
183
+ oh = {
184
+ '1st' => 1, '2nd' => 2, '3rd' => 3, '21st' => 21, '22nd' => 22,
185
+ '23rd' => 23, '31st' => 31 }
186
+ %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 ]
187
+ .each { |i| oh["#{i}th"] = i.to_i }
188
+ %w[
189
+ first second third fourth fifth sixth seventh eighth ninth tenth
190
+ eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth
191
+ eighteenth nineteenth twentieth twenty-first twenty-second twenty-third
192
+ twenty-fourth twenty-fifth twenty-fifth twenty-sixth twenty-seventh
193
+ twenty-eighth twenty-ninth thirtieth thirty-first ]
194
+ .each_with_index { |e, i| oh[e] = i + 1 }
195
+ ORDINALS = oh
160
196
 
161
197
  # piece parsers bottom to top
162
198
 
199
+ def _from(i); rex(nil, i, /\s*from\s+/i); end
200
+ def _every(i); rex(nil, i, /\s*(every)\s+/i); end
201
+ def _at(i); rex(nil, i, /\s*at\s+/i); end
202
+ def _in(i); rex(nil, i, /\s*(in|on)\s+/i); end
203
+ def _to(i); rex(nil, i, /\s*to\s+/i); end
204
+ def _dash(i); rex(nil, i, /-\s*/i); end
205
+ def _and(i); rex(nil, i, /\s*and\s+/i); end
206
+ def _on(i); rex(nil, i, /\s*on\s+/i); end
207
+
208
+ def _and_or_comma(i)
209
+ rex(nil, i, /\s*(,?\s*and\s|,?\s*or\s|,)\s*/i)
210
+ end
211
+ def _at_comma(i)
212
+ rex(nil, i, /\s*(at\s|,|)\s*/i)
213
+ end
214
+ def _to_through(i)
215
+ rex(nil, i, /\s*(to|through)\s+/i)
216
+ end
217
+
218
+ def integer(i); rex(:int, i, /\d+\s*/); end
219
+
220
+ def tz_name(i)
221
+ rex(nil, i,
222
+ /\s*[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}(\s+|$)/)
223
+ end
224
+ def tz_delta(i)
225
+ rex(nil, i,
226
+ /\s*[-+]([01][0-9]|2[0-4]):?(00|15|30|45)(\s+|$)/)
227
+ end
228
+ def tzone(i)
229
+ alt(:tzone, i, :tz_delta, :tz_name)
230
+ end
231
+
232
+ def and_named_digits(i)
233
+ rex(:xxx, i, 'TODO')
234
+ end
235
+
236
+ def dname(i)
237
+ rex(:dname, i, /(s(ec(onds?)?)?|m(in(utes?)?)?)\s+/i)
238
+ end
239
+ def named_digit(i)
240
+ seq(:named_digit, i, :dname, :integer)
241
+ end
242
+ def named_digits(i)
243
+ seq(nil, i, :named_digit, '+', :and_named_digits, '*')
244
+ end
245
+
163
246
  def am_pm(i)
164
- rex(:am_pm, i, / *(am|pm)/i)
247
+ rex(:am_pm, i, /\s*(am|pm|dark)\s*/i)
248
+ end
249
+
250
+ def nminute(i)
251
+ rex(:nminute, i, /(#{NMINUTES.keys.join('|')})\s*/i)
252
+ end
253
+ def nhour(i)
254
+ rex(:nhour, i, /(#{NUMS.join('|')})\s*/i)
255
+ end
256
+ def numeral_hour(i)
257
+ seq(:numeral_hour, i, :nhour, :am_pm, '?', :nminute, '?')
258
+ end
259
+
260
+ def named_hour(i)
261
+ rex(:named_hour, i, /(#{NHOURS.keys.join('|')})/i)
262
+ end
263
+
264
+ def shour(i)
265
+ rex(:shour, i, /(2[0-4]|[01]?[0-9])/)
266
+ end
267
+ def simple_hour(i)
268
+ seq(:simple_hour, i, :shour, :am_pm, '?')
165
269
  end
166
270
 
167
271
  def digital_hour(i)
168
272
  rex(:digital_hour, i, /(2[0-4]|[01][0-9]):?[0-5]\d/)
169
273
  end
170
274
 
171
- def _simple_hour(i)
172
- rex(:sh, i, /(2[0-4]|[01]?[0-9])/)
275
+ def at_point(i)
276
+ alt(nil, i,
277
+ :digital_hour, :simple_hour, :named_hour, :numeral_hour,
278
+ :named_digits)
173
279
  end
174
- def simple_hour(i)
175
- seq(:simple_hour, i, :_simple_hour, :am_pm, '?')
280
+
281
+ def weekday(i)
282
+ rex(:weekday, i, /(#{WEEKDAYS.reverse.join('|')})\s*/i)
176
283
  end
177
284
 
178
- def _numeral_hour(i)
179
- rex(:nh, i, /(#{NUMS.join('|')})/i)
285
+ def and_at(i)
286
+ seq(nil, i, :_and_or_comma, :at_point)
180
287
  end
181
- def numeral_hour(i)
182
- seq(:numeral_hour, i, :_numeral_hour, :am_pm, '?')
288
+
289
+ def _intervals(i)
290
+ rex(:intervals, i,
291
+ /(
292
+ y(ears?)?|months?|w(eeks?)?|d(ays?)?|
293
+ h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?
294
+ )(\s+|$)/ix)
183
295
  end
184
296
 
185
- def name_hour(i)
186
- rex(:name_hour, i, /(#{NHOURS.keys.join('|')})/i)
297
+ def sinterval(i)
298
+ rex(:sinterval, i,
299
+ /(year|month|week|day|hour|min(ute)?|sec(ond)?)(\s+|$)/i)
300
+ end
301
+ def ninterval(i)
302
+ seq(:ninterval, i, :integer, :_intervals)
187
303
  end
188
304
 
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
305
+ def ordinal(i)
306
+ rex(:ordinal, i, /\s*(#{ORDINALS.keys.join('|')})\s*/)
307
+ end
192
308
 
193
- def range_sep(i); rex(nil, i, / *- *| +(to|through) +/); end
309
+ def _mod(i); rex(nil, i, /\s*month\s+on\s+days?\s+/i); end
310
+ def _oftm(i); rex(nil, i, /\s*(day\s)?\s*of\s+the\s+month\s*/i); end
194
311
 
195
- def day_range(i)
196
- seq(:day_range, i, :name_day, :range_sep, :name_day)
312
+ def dom(i)
313
+ rex(:int, i, /([12][0-9]|3[01]|[0-9])/)
314
+ end
315
+ def and_or_dom(i)
316
+ seq(nil, i, :_and_or_comma, :dom)
317
+ end
318
+ def dom_list(i)
319
+ seq(:dom_list, i, :dom, :and_or_dom, '*')
197
320
  end
198
321
 
199
- def _tz_name(i)
200
- rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/)
322
+ def dom_mod(i) # every month on day
323
+ seq(:dom, i, :_mod, :dom_list)
324
+ end
325
+ def dom_noftm(i) # every nth of month
326
+ seq(:dom, i, :ordinal, :_oftm)
201
327
  end
202
- def _tz_delta(i)
203
- rex(nil, i, /[-+]([01][0-9]|2[0-4]):?(00|15|30|45)/)
328
+ def day_of_month(i)
329
+ alt(nil, i, :dom_noftm, :dom_mod)
204
330
  end
205
- def _tz(i); alt(:tz, i, :_tz_delta, :_tz_name); end
206
331
 
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)
332
+ def dow_class(i)
333
+ rex(:dow_class, i, /(weekday)(\s+|$)/i)
215
334
  end
216
335
 
217
- def flag(i); rex(:flag, i, /(every|from|at|after|on|in)/i); end
336
+ def dow(i)
337
+ seq(:dow, i, :weekday)
338
+ end
339
+ def and_or_dow(i)
340
+ seq(nil, i, :_and_or_comma, :dow)
341
+ end
342
+ def dow_list(i)
343
+ seq(:dow_list, i, :dow, :and_or_dow, '*')
344
+ end
218
345
 
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)
346
+ def to_dow_range(i)
347
+ seq(:dow_range, i, :weekday, :_to_through, :weekday)
348
+ end
349
+ def dash_dow_range(i)
350
+ seq(:dow_range, i, :weekday, :_dash, :weekday)
351
+ end
352
+ def dow_range(i)
353
+ alt(nil, i, :to_dow_range, :dash_dow_range)
354
+ end
355
+
356
+ def day_of_week(i)
357
+ alt(nil, i, :dow_range, :dow_list, :dow_class)
358
+ end
359
+
360
+ def interval(i)
361
+ alt(nil, i, :sinterval, :ninterval)
362
+ end
363
+
364
+ def every_object(i)
365
+ alt(nil, i, :day_of_month, :interval, :day_of_week)
366
+ end
367
+ def from_object(i)
368
+ alt(nil, i, :interval, :to_dow_range)
369
+ end
370
+
371
+ def tz(i)
372
+ seq(nil, i, :_in, '?', :tzone)
373
+ end
374
+ def on(i)
375
+ seq(:on, i, :_on, :weekday, :at_point, :and_at, '*')
376
+ end
377
+ def at(i)
378
+ seq(:at, i, :_at_comma, :at_point, :and_at, '*')
379
+ end
380
+ def from(i)
381
+ seq(:from, i, :_from, :from_object)
382
+ end
383
+ def every(i)
384
+ seq(:every, i, :_every, :every_object)
385
+ end
386
+
387
+ def at_from(i)
388
+ seq(nil, i, :at, :from, :tz, '?')
389
+ end
390
+ def at_every(i)
391
+ seq(nil, i, :at, :every, :tz, '?')
392
+ end
393
+
394
+ def from_at(i)
395
+ seq(nil, i, :from, :at, '?', :tz, '?')
227
396
  end
228
397
 
229
- def sugar(i); rex(nil, i, /(and|or|[, \t]+)/i); end
398
+ def every_(i)
399
+ seq(nil, i, :every, :tz, '?')
400
+ end
401
+ def every_on(i)
402
+ seq(nil, i, :every, :on, :tz, '?')
403
+ end
404
+ def every_at(i)
405
+ seq(nil, i, :every, :at, :tz, '?')
406
+ end
230
407
 
231
- def elt(i); alt(nil, i, :sugar, :datum); end
232
- def nat(i); rep(:nat, i, :elt, 1); end
408
+ def nat(i)
409
+ alt(:nat, i,
410
+ :every_at, :every_on, :every_,
411
+ :from_at,
412
+ :at_every, :at_from)
413
+ end
233
414
 
234
415
  # rewrite parsed tree
235
416
 
236
- def rewrite_nat(t)
417
+ #def _rewrite_single(t)
418
+ # [ t.name, rewrite(t.sublookup(nil)) ]
419
+ #end
420
+ def _rewrite_children(t)
421
+ t.subgather(nil).collect { |tt| rewrite(tt) }
422
+ end
423
+ def _rewrite_multiple(t)
424
+ [ t.name, _rewrite_children(t) ]
425
+ end
426
+ def _rewrite_child(t)
427
+ rewrite(t.sublookup(nil))
428
+ end
429
+
430
+ def rewrite_int(t); t.string.to_i; end
431
+
432
+ def rewrite_tzone(t)
433
+
434
+ [ :tz, t.strim ]
435
+ end
436
+
437
+ def rewrite_sinterval(t)
438
+
439
+ [ :interval, 1, t.strim ]
440
+ end
441
+
442
+ def rewrite_ninterval(t)
443
+
444
+ [ :interval,
445
+ t.sublookup(:int).string.to_i,
446
+ t.sublookup(:intervals).strim ]
447
+ end
448
+
449
+ def rewrite_named_digit(t)
450
+
451
+ i = t.sublookup(:int).string.to_i
452
+
453
+ case n = t.sublookup(:dname).strim
454
+ when /^s/ then [ '*', '*', i ]
455
+ when /^m/ then [ '*', i ]
456
+ end
457
+ end
458
+
459
+ def rewrite_named_hour(t)
460
+ NHOURS[t.strim.downcase]
461
+ end
462
+ def rewrite_numeral_hour(t)
463
+ vs = t.subgather(nil).collect { |st| st.strim.downcase }
464
+ v = NUMS.index(vs[0])
465
+ v += 12 if vs[1] == 'pm'
466
+ m = NMINUTES[vs[2]] || 0
467
+ [ v, m ]
468
+ end
469
+ def rewrite_simple_hour(t)
470
+ vs = t.subgather(nil).collect { |st| st.strim.downcase }
471
+ v = vs[0].to_i
472
+ v += 12 if vs[1] == 'pm'
473
+ [ v, 0 ]
474
+ end
475
+ def rewrite_digital_hour(t)
476
+ m = t.string.match(/(\d\d?):?(\d\d)/)
477
+ [ m[1].to_i, m[2].to_i ]
478
+ end
479
+
480
+ def rewrite_weekday(t)
481
+
482
+ WEEKDAYS.index(t.strim.downcase[0, 3])
483
+ end
484
+
485
+ def rewrite_ordinal(t); ORDINALS[t.strim]; end
486
+
487
+ def rewrite_dom(t)
237
488
 
238
489
  #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 }
490
+ [ :day_of_month,
491
+ *_rewrite_children(t).flatten.select { |e| e.is_a?(Integer) } ]
492
+ end
493
+
494
+ alias rewrite_dow _rewrite_child
495
+
496
+ def rewrite_dom_list(t); [ :dom_list, *_rewrite_children(t) ]; end
497
+ def rewrite_dow_list(t); [ :dow_list, *_rewrite_children(t) ]; end
498
+
499
+ def rewrite_dow_class(t)
500
+
501
+ [ :dow_range, 1, 5 ] # only "weekday" for now
502
+ end
503
+
504
+ def rewrite_dow_range(t)
505
+
506
+ tts = t.subgather(nil)
507
+
508
+ [ :dow_range, rewrite(tts[0]), rewrite(tts[1]) ]
509
+ end
510
+
511
+ alias rewrite_on _rewrite_multiple
512
+ alias rewrite_at _rewrite_multiple
513
+
514
+ alias rewrite_from _rewrite_child
515
+ alias rewrite_every _rewrite_child
516
+
517
+ def rewrite_nat(t)
518
+
519
+ t.subgather(nil).collect { |tt| rewrite(tt) }
520
+ #.tap { |x| pp x }
268
521
  end
269
522
  end
270
523
  end
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.1
4
+ version: 1.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Mettraux
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-27 00:00:00.000000000 Z
11
+ date: 2020-06-01 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.3'
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.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: et-orbi
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -117,8 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
117
  - !ruby/object:Gem::Version
118
118
  version: '0'
119
119
  requirements: []
120
- rubyforge_project:
121
- rubygems_version: 2.5.2.3
120
+ rubygems_version: 3.0.3
122
121
  signing_key:
123
122
  specification_version: 4
124
123
  summary: time tools for flor