fugit 1.3.3 → 1.3.8

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: 0e67fe1fbf7a5e0213b828cc78c59d6cf5b36a6e
4
- data.tar.gz: 920ea06ef202094e9b1c9c719fb06c55e0926b51
2
+ SHA256:
3
+ metadata.gz: 86abe0dec557524b3ae8c7947f48ab15a52e49b240ecdb3f4103c916348e6e4c
4
+ data.tar.gz: d440e0d52d49b6ad2893b05c269b7c6f96936cfcb4a3904750b3204568d2344a
5
5
  SHA512:
6
- metadata.gz: d6f4c28d4e280b73a0b5a3c6bae84e328e06fa33cb573bc8fc1a3660e1c921dd666c793fd17a18f10bc4d630d5db2473c2d75659def60bb6fd7342c9e3dfbb51
7
- data.tar.gz: 40ae39ba32717fcc2d0148d2e4a63a2cd179fa61ef5de13670057021b77a95c9bec1ae7f299a3117602e63a1b02c5b1de683748ef793fa74ec394a4a7861992c
6
+ metadata.gz: 13fe87e6bc9d37cf3822c1bed665651be254b8e4a3d262f20950dc6f14d6170495721a0cd79ca8324b35048d90ce71d996b654f23da2b4780df536b9f4bd8071
7
+ data.tar.gz: 2623e274a96a689639fbbbcc22f77bc86f8bcc0f49644e459673a54eadfd39deecf8158b85948b6969339b7d6c9e2554537aa4c6575f63a56fd0333626b0ed88
@@ -2,6 +2,33 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.3.8 released 2020-08-06
6
+
7
+ * Parse 'every day at 8:30' and ' at 8:30 pm', gh-42
8
+
9
+
10
+ ## fugit 1.3.7 released 2020-08-05
11
+
12
+ * Parse 'every 12 hours at minute 50', gh-41
13
+
14
+
15
+ ## fugit 1.3.6 released 2020-06-01
16
+
17
+ * Introduce new nat syntaxed, gh-38
18
+ * Rework nat parser
19
+
20
+
21
+ ## fugit 1.3.5 released 2020-05-07
22
+
23
+ * Implement cron @noon, gh-37
24
+ * Normalize "every x", gh-37
25
+
26
+
27
+ ## fugit 1.3.4 released 2020-04-06
28
+
29
+ * Prevent #rough_frequency returning 0, gh-36
30
+
31
+
5
32
  ## fugit 1.3.3 released 2019-08-29
6
33
 
7
34
  * Fix Cron#match?(t) with respect to the cron's timezone, gh-31
data/CREDITS.md CHANGED
@@ -1,9 +1,12 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
+ * Jérôme Dalbert https://github.com/jeromedalbert gh-41, gh-42
5
+ * Danny Ben Shitrit https://github.com/DannyBen nat variants, gh-38
6
+ * Dominik Sander https://github.com/dsander #rough_frequency 0, gh-36
4
7
  * Milovan Zogovic https://github.com/assembler Cron#match? vs TZ, gh-31
5
8
  * Jessica Stokes https://github.com/ticky 0-24 issue with cron, gh-30
6
- * Shai Coleman https://github.com/shaicoleman parse_nat enhancements, gh-24, gh-25, and gh-28
9
+ * Shai Coleman https://github.com/shaicoleman parse_nat enhancements, gh-24, gh-25, gh-28, and gh-37
7
10
  * Jan Stevens https://github.com/JanStevens Fugit.parse('every 15 minutes') gh-22
8
11
  * Fabio Pitino https://github.com/hspazio nil on February 30 gh-21
9
12
  * 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
@@ -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
@@ -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)`
@@ -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
@@ -188,6 +232,29 @@ p d.to_plain_s # => "2Y2M1D5h3600s"
188
232
  p Fugit::Duration.parse('1y2M1d4h').to_sec # => 36820800
189
233
  ```
190
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
+
191
258
  The `to_*_s` methods are also available as class methods:
192
259
  ```ruby
193
260
  p Fugit::Duration.to_plain_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.3'
4
+ VERSION = '1.3.8'
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)
@@ -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
 
@@ -744,7 +757,7 @@ module Fugit
744
757
 
745
758
  def rewrite_tz(t)
746
759
 
747
- s = t.string.strip
760
+ s = t.strim
748
761
  z = EtOrbi.get_tzone(s)
749
762
 
750
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,461 @@ 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
76
+ def assemble_cron(h)
90
77
 
91
- h[:min] ||= [ 0 ]
92
- h[:min].uniq!
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]
93
85
 
94
- h[:hou].uniq!;
95
- h[:hou].sort!
86
+ Fugit::Cron.parse(s.join(' '))
87
+ end
96
88
 
97
- h[:dow].sort! if h[:dow]
89
+ def eone(e); e1 = e[1]; e1 == 1 ? '*' : "*/#{e1}"; end
98
90
 
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]
91
+ def parse_interval_elt(e, opts, h)
105
92
 
106
- s = a.join(' ')
93
+ e1 = e[1]
107
94
 
108
- Fugit::Cron.parse(s)
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
109
125
  end
110
126
 
111
- def process_duration(h, interval, value)
127
+ def parse_dow_list_elt(e, opts, h)
112
128
 
113
- send("process_duration_#{interval}", h, value)
129
+ h[:hms] ||= [ [ 0, 0 ] ]
130
+ h[:dow] = e[1..-1].collect(&:to_s).sort.join(',')
114
131
  end
115
132
 
116
- def process_duration_mon(h, value)
133
+ def parse_dow_range_elt(e, opts, h)
117
134
 
118
- h[:hou] = [ 0 ]
119
- h[:dom] = [ 1 ]
120
- h[:mon] = [ value == 1 ? '*' : "*/#{value}" ]
135
+ h[:hms] ||= [ [ 0, 0 ] ]
136
+ h[:dow] = e[1] == e[2] ? e[1] : "#{e[1]}-#{e[2]}"
121
137
  end
122
138
 
123
- def process_duration_day(h, value)
139
+ def parse_day_of_month_elt(e, opts, h)
124
140
 
125
- h[:hou] = [ 0 ]
126
- h[:dom] = [ value == 1 ? '*' : "*/#{value}" ]
141
+ h[:dom] = e[1..-1].join(',')
127
142
  end
128
143
 
129
- def process_duration_hou(h, value)
144
+ def parse_at_elt(e, opts, h)
130
145
 
131
- h[:hou] = [ value == 1 ? '*' : "*/#{value}" ]
146
+ (h[:hms] ||= []).concat(e[1])
147
+
148
+ l = h[:hms].last
149
+ h[:sec] = l.pop if l.size > 2
132
150
  end
133
151
 
134
- def process_duration_min(h, value)
152
+ def parse_on_elt(e, opts, h)
153
+
154
+ e1 = e[1]
155
+ h[:dow] = e1[0]
156
+ h[:hms] = [ e1[1] ]
135
157
 
136
- h[:hou] = [ '*' ]
137
- h[:min] = [ value == 1 ? '*' : "*/#{value}" ]
158
+ l = h[:hms].last
159
+ h[:sec] = l.pop if l.size > 2
138
160
  end
139
161
 
140
- def process_duration_sec(h, value)
162
+ def parse_tz_elt(e, opts, h)
141
163
 
142
- h[:hou] = [ '*' ]
143
- h[:min] = [ '*' ]
144
- h[:sec] = [ value == 1 ? '*' : "*/#{value}" ]
164
+ h[:tz] = e[1]
145
165
  end
146
166
  end
147
167
 
148
168
  module Parser include Raabro
149
169
 
150
170
  NUMS = %w[
151
- zero
152
- one two three four five six seven eight nine
153
- ten eleven twelve ]
171
+ zero one two three four five six seven eight nine ten eleven twelve ]
154
172
 
155
173
  WEEKDAYS =
156
174
  Fugit::Cron::Parser::WEEKDS + Fugit::Cron::Parser::WEEKDAYS
157
175
 
158
- NHOURS =
159
- { 'noon' => [ 12, 0 ], 'midnight' => [ 0, 0 ] }
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
160
200
 
161
201
  # piece parsers bottom to top
162
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)
214
+ end
215
+ def _at_comma(i)
216
+ rex(nil, i, /\s*(at\s|,|)\s*/i)
217
+ end
218
+ def _to_through(i)
219
+ rex(nil, i, /\s*(to|through)\s+/i)
220
+ 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+|$)/)
227
+ end
228
+ def tz_delta(i)
229
+ rex(nil, i,
230
+ /\s*[-+]([01][0-9]|2[0-4]):?(00|15|30|45)(\s+|$)/)
231
+ end
232
+ def tzone(i)
233
+ alt(:tzone, i, :tz_delta, :tz_name)
234
+ end
235
+
236
+ def and_named_digits(i)
237
+ rex(:xxx, i, 'TODO')
238
+ end
239
+
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)
245
+ end
246
+ def named_digits(i)
247
+ seq(nil, i, :named_digit, '+', :and_named_digits, '*')
248
+ end
249
+
163
250
  def am_pm(i)
164
- rex(:am_pm, i, / *(am|pm)/i)
251
+ rex(:am_pm, i, /\s*(am|pm|dark)\s*/i)
165
252
  end
166
253
 
167
- def digital_hour(i)
168
- rex(:digital_hour, i, /(2[0-4]|[01][0-9]):?[0-5]\d/)
254
+ def nminute(i)
255
+ rex(:nminute, i, /(#{NMINUTES.keys.join('|')})\s*/i)
256
+ end
257
+ def nhour(i)
258
+ rex(:nhour, i, /(#{NUMS.join('|')})\s*/i)
259
+ end
260
+ def numeral_hour(i)
261
+ seq(:numeral_hour, i, :nhour, :am_pm, '?', :nminute, '?')
262
+ end
263
+
264
+ def named_hour(i)
265
+ rex(:named_hour, i, /(#{NHOURS.keys.join('|')})/i)
169
266
  end
170
267
 
171
- def _simple_hour(i)
172
- rex(:sh, i, /(2[0-4]|[01]?[0-9])/)
268
+ def shour(i)
269
+ rex(:shour, i, /(2[0-4]|[01]?[0-9])/)
173
270
  end
174
271
  def simple_hour(i)
175
- seq(:simple_hour, i, :_simple_hour, :am_pm, '?')
272
+ seq(:simple_hour, i, :shour, :am_pm, '?')
176
273
  end
177
274
 
178
- def _numeral_hour(i)
179
- rex(:nh, i, /(#{NUMS.join('|')})/i)
275
+ def dig_hour_b(i); rex(nil, i, /(2[0-4]|[01][0-9]|[0-9]):[0-5]\d/); end
276
+ def dig_hour_a(i); rex(nil, i, /(2[0-4]|[01][0-9])[0-5]\d/); end
277
+ def dig_hour(i); alt(nil, i, :dig_hour_a, :dig_hour_b); end
278
+ #
279
+ def digital_hour(i)
280
+ seq(:digital_hour, i, :dig_hour, :am_pm, '?')
180
281
  end
181
- def numeral_hour(i)
182
- seq(:numeral_hour, i, :_numeral_hour, :am_pm, '?')
282
+
283
+ def at_point(i)
284
+ alt(nil, i,
285
+ :digital_hour, :simple_hour, :named_hour, :numeral_hour,
286
+ :named_digits)
183
287
  end
184
288
 
185
- def name_hour(i)
186
- rex(:name_hour, i, /(#{NHOURS.keys.join('|')})/i)
289
+ def weekday(i)
290
+ rex(:weekday, i, /(#{WEEKDAYS.reverse.join('|')})\s*/i)
187
291
  end
188
292
 
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
293
+ def and_at(i)
294
+ seq(nil, i, :_and_or_comma, :at_point)
295
+ end
192
296
 
193
- def range_sep(i); rex(nil, i, / *- *| +(to|through) +/); end
297
+ def _intervals(i)
298
+ rex(:intervals, i,
299
+ /(
300
+ y(ears?)?|months?|w(eeks?)?|d(ays?)?|
301
+ h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?
302
+ )(\s+|$)/ix)
303
+ end
194
304
 
195
- def day_range(i)
196
- seq(:day_range, i, :name_day, :range_sep, :name_day)
305
+ def sinterval(i)
306
+ rex(:sinterval, i,
307
+ /(year|month|week|day|hour|min(ute)?|sec(ond)?)(\s+|$)/i)
308
+ end
309
+ def ninterval(i)
310
+ seq(:ninterval, i, :integer, :_intervals)
197
311
  end
198
312
 
199
- def _tz_name(i)
200
- rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/)
313
+ def ordinal(i)
314
+ rex(:ordinal, i, /\s*(#{ORDINALS.keys.join('|')})\s*/)
201
315
  end
202
- def _tz_delta(i)
203
- rex(nil, i, /[-+]([01][0-9]|2[0-4]):?(00|15|30|45)/)
316
+
317
+ def _mod(i); rex(nil, i, /\s*month\s+on\s+days?\s+/i); end
318
+ def _oftm(i); rex(nil, i, /\s*(day\s)?\s*of\s+the\s+month\s*/i); end
319
+
320
+ def dom(i)
321
+ rex(:int, i, /([12][0-9]|3[01]|[0-9])/)
322
+ end
323
+ def and_or_dom(i)
324
+ seq(nil, i, :_and_or_comma, :dom)
325
+ end
326
+ def dom_list(i)
327
+ seq(:dom_list, i, :dom, :and_or_dom, '*')
204
328
  end
205
- def _tz(i); alt(:tz, i, :_tz_delta, :_tz_name); end
206
329
 
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)
330
+ def dom_mod(i) # every month on day
331
+ seq(:dom, i, :_mod, :dom_list)
332
+ end
333
+ def dom_noftm(i) # every nth of month
334
+ seq(:dom, i, :ordinal, :_oftm)
335
+ end
336
+ def day_of_month(i)
337
+ alt(nil, i, :dom_noftm, :dom_mod)
215
338
  end
216
339
 
217
- def flag(i); rex(:flag, i, /(every|from|at|after|on|in)/i); end
340
+ def dow_class(i)
341
+ rex(:dow_class, i, /(weekday)(\s+|$)/i)
342
+ end
218
343
 
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)
344
+ def dow(i)
345
+ seq(:dow, i, :weekday)
346
+ end
347
+ def and_or_dow(i)
348
+ seq(nil, i, :_and_or_comma, :dow)
349
+ end
350
+ def dow_list(i)
351
+ seq(:dow_list, i, :dow, :and_or_dow, '*')
352
+ end
353
+
354
+ def to_dow_range(i)
355
+ seq(:dow_range, i, :weekday, :_to_through, :weekday)
356
+ end
357
+ def dash_dow_range(i)
358
+ seq(:dow_range, i, :weekday, :_dash, :weekday)
359
+ end
360
+ def dow_range(i)
361
+ alt(nil, i, :to_dow_range, :dash_dow_range)
362
+ end
363
+
364
+ def day_of_week(i)
365
+ alt(nil, i, :dow_range, :dow_list, :dow_class)
366
+ end
367
+
368
+ def interval(i)
369
+ alt(nil, i, :sinterval, :ninterval)
370
+ end
371
+
372
+ def every_object(i)
373
+ alt(nil, i, :day_of_month, :interval, :day_of_week)
374
+ end
375
+ def from_object(i)
376
+ alt(nil, i, :interval, :to_dow_range)
377
+ end
378
+
379
+ def tz(i)
380
+ seq(nil, i, :_in, '?', :tzone)
381
+ end
382
+ def on(i)
383
+ seq(:on, i, :_on, :weekday, :at_point, :and_at, '*')
384
+ end
385
+ def at(i)
386
+ seq(:at, i, :_at_comma, :at_point, :and_at, '*')
387
+ end
388
+ def from(i)
389
+ seq(:from, i, :_from, :from_object)
390
+ end
391
+ def every(i)
392
+ seq(:every, i, :_every, :every_object)
393
+ end
394
+
395
+ def at_from(i)
396
+ seq(nil, i, :at, :from, :tz, '?')
397
+ end
398
+ def at_every(i)
399
+ seq(nil, i, :at, :every, :tz, '?')
400
+ end
401
+
402
+ def from_at(i)
403
+ seq(nil, i, :from, :at, '?', :tz, '?')
227
404
  end
228
405
 
229
- def sugar(i); rex(nil, i, /(and|or|[, \t]+)/i); end
406
+ def every_(i)
407
+ seq(nil, i, :every, :tz, '?')
408
+ end
409
+ def every_on(i)
410
+ seq(nil, i, :every, :on, :tz, '?')
411
+ end
412
+ def every_at(i)
413
+ seq(nil, i, :every, :at, :tz, '?')
414
+ end
230
415
 
231
- def elt(i); alt(nil, i, :sugar, :datum); end
232
- def nat(i); rep(:nat, i, :elt, 1); end
416
+ def nat(i)
417
+ alt(:nat, i,
418
+ :every_at, :every_on, :every_,
419
+ :from_at,
420
+ :at_every, :at_from)
421
+ end
233
422
 
234
423
  # rewrite parsed tree
235
424
 
236
- def rewrite_nat(t)
425
+ #def _rewrite_single(t)
426
+ # [ t.name, rewrite(t.sublookup(nil)) ]
427
+ #end
428
+ def _rewrite_children(t)
429
+ t.subgather(nil).collect { |tt| rewrite(tt) }
430
+ end
431
+ def _rewrite_multiple(t)
432
+ [ t.name, _rewrite_children(t) ]
433
+ end
434
+ def _rewrite_child(t)
435
+ rewrite(t.sublookup(nil))
436
+ end
437
+
438
+ def rewrite_int(t); t.string.to_i; end
439
+
440
+ def rewrite_tzone(t)
441
+
442
+ [ :tz, t.strim ]
443
+ end
444
+
445
+ def rewrite_sinterval(t)
446
+
447
+ [ :interval, 1, t.strim ]
448
+ end
449
+
450
+ def rewrite_ninterval(t)
451
+
452
+ [ :interval,
453
+ t.sublookup(:int).string.to_i,
454
+ t.sublookup(:intervals).strim ]
455
+ end
456
+
457
+ def rewrite_named_digit(t)
458
+
459
+ i = t.sublookup(:int).string.to_i
460
+
461
+ case n = t.sublookup(:dname).strim
462
+ when /^s/ then [ '*', '*', i ]
463
+ when /^m/ then [ '*', i ]
464
+ end
465
+ end
466
+
467
+ def rewrite_named_hour(t)
468
+ NHOURS[t.strim.downcase]
469
+ end
470
+ def rewrite_numeral_hour(t)
471
+ vs = t.subgather(nil).collect { |st| st.strim.downcase }
472
+ v = NUMS.index(vs[0])
473
+ v += 12 if vs[1] == 'pm'
474
+ m = NMINUTES[vs[2]] || 0
475
+ [ v, m ]
476
+ end
477
+ def rewrite_simple_hour(t)
478
+ vs = t.subgather(nil).collect { |st| st.strim.downcase }
479
+ v = vs[0].to_i
480
+ v += 12 if vs[1] == 'pm'
481
+ [ v, 0 ]
482
+ end
483
+ def rewrite_digital_hour(t)
484
+ m = t.string.match(/(\d\d?):?(\d\d)(\s+pm)?/i)
485
+ hou = m[1].to_i; hou += 12 if m[3] && hou < 12
486
+ min = m[2].to_i
487
+ [ hou, min ]
488
+ end
489
+
490
+ def rewrite_weekday(t)
491
+
492
+ WEEKDAYS.index(t.strim.downcase[0, 3])
493
+ end
494
+
495
+ def rewrite_ordinal(t); ORDINALS[t.strim]; end
496
+
497
+ def rewrite_dom(t)
237
498
 
238
499
  #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 }
500
+ [ :day_of_month,
501
+ *_rewrite_children(t).flatten.select { |e| e.is_a?(Integer) } ]
502
+ end
503
+
504
+ alias rewrite_dow _rewrite_child
505
+
506
+ def rewrite_dom_list(t); [ :dom_list, *_rewrite_children(t) ]; end
507
+ def rewrite_dow_list(t); [ :dow_list, *_rewrite_children(t) ]; end
508
+
509
+ def rewrite_dow_class(t)
510
+
511
+ [ :dow_range, 1, 5 ] # only "weekday" for now
512
+ end
513
+
514
+ def rewrite_dow_range(t)
515
+
516
+ tts = t.subgather(nil)
517
+
518
+ [ :dow_range, rewrite(tts[0]), rewrite(tts[1]) ]
519
+ end
520
+
521
+ alias rewrite_on _rewrite_multiple
522
+ alias rewrite_at _rewrite_multiple
523
+
524
+ alias rewrite_from _rewrite_child
525
+ alias rewrite_every _rewrite_child
526
+
527
+ def rewrite_nat(t)
528
+
529
+ t.subgather(nil).collect { |tt| rewrite(tt) }
530
+ #.tap { |x| pp x }
268
531
  end
269
532
  end
270
533
  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.3
4
+ version: 1.3.8
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-08-29 00:00:00.000000000 Z
11
+ date: 2020-08-06 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.1.2
122
121
  signing_key:
123
122
  specification_version: 4
124
123
  summary: time tools for flor