fugit 1.3.2 → 1.3.7

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: a1f0b03b24c2f3467b85203b569060a3bd1e43e5
4
- data.tar.gz: d7b5598b726f4dfc49e9ab73bc970ce4e02e6944
2
+ SHA256:
3
+ metadata.gz: d3e29ff88eb9d7ce1e5214af33683f577967b86ded3ee9f90be8c628fa982e40
4
+ data.tar.gz: 7adb64aaee08b7c354a3e619eda0edb96696ad4fd1d19d39c06e9a9768d058b7
5
5
  SHA512:
6
- metadata.gz: 3e6b92e0d2805d0bc4cc109342d7e4692b460e9d805ffff44c17bd645bddf88ea78f51b9d092fce6cbfb7ff31f62ca53dd88f009cc25c1759210e26c267d065c
7
- data.tar.gz: 52b960a0e3ceb3e0fdaec890e4b8731bc17a25bce5d161cfdfbb1b7a5d7ddc6126284bffa7262239c557e150b7b58fcccb9fff5188c1306ec2ba18d1ca7f00a3
6
+ metadata.gz: db82aa99da670c23ba90424e015afcc8e0b5fff419d62806439bd94f171b8e0df18bbf0d3531343e814d2f9a1709a577f91f0d12024be3654df0f73922a255d5
7
+ data.tar.gz: 5bd2ae16ad1e244a6a4f26f24ba8f5291c0e1ee3271b5ccbe86c1dcd3131a383731829c838826f8dbc94ecd6e14fa6d918576a3e2347882fb635e50c8c9c3ce4
@@ -2,6 +2,33 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.3.7 released 2020-08-05
6
+
7
+ * Parse 'every 12 hours at minute 50', gh-41
8
+
9
+
10
+ ## fugit 1.3.6 released 2020-06-01
11
+
12
+ * Introduce new nat syntaxed, gh-38
13
+ * Rework nat parser
14
+
15
+
16
+ ## fugit 1.3.5 released 2020-05-07
17
+
18
+ * Implement cron @noon, gh-37
19
+ * Normalize "every x", gh-37
20
+
21
+
22
+ ## fugit 1.3.4 released 2020-04-06
23
+
24
+ * Prevent #rough_frequency returning 0, gh-36
25
+
26
+
27
+ ## fugit 1.3.3 released 2019-08-29
28
+
29
+ * Fix Cron#match?(t) with respect to the cron's timezone, gh-31
30
+
31
+
5
32
  ## fugit 1.3.2 released 2019-08-14
6
33
 
7
34
  * Allow for "* 0-24 * * *", gh-30
data/CREDITS.md CHANGED
@@ -1,8 +1,12 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
+ * Jérôme Dalbert https://github.com/jeromedalbert every 12h at min 50, gh-41
5
+ * Danny Ben Shitrit https://github.com/DannyBen nat variants, gh-38
6
+ * Dominik Sander https://github.com/dsander #rough_frequency 0, gh-36
7
+ * Milovan Zogovic https://github.com/assembler Cron#match? vs TZ, gh-31
4
8
  * Jessica Stokes https://github.com/ticky 0-24 issue with cron, gh-30
5
- * 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
6
10
  * Jan Stevens https://github.com/JanStevens Fugit.parse('every 15 minutes') gh-22
7
11
  * Fabio Pitino https://github.com/hspazio nil on February 30 gh-21
8
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.2'
4
+ VERSION = '1.3.7'
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
 
@@ -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,455 @@ 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)
252
+ end
253
+
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)
266
+ end
267
+
268
+ def shour(i)
269
+ rex(:shour, i, /(2[0-4]|[01]?[0-9])/)
270
+ end
271
+ def simple_hour(i)
272
+ seq(:simple_hour, i, :shour, :am_pm, '?')
165
273
  end
166
274
 
167
275
  def digital_hour(i)
168
276
  rex(:digital_hour, i, /(2[0-4]|[01][0-9]):?[0-5]\d/)
169
277
  end
170
278
 
171
- def _simple_hour(i)
172
- rex(:sh, i, /(2[0-4]|[01]?[0-9])/)
279
+ def at_point(i)
280
+ alt(nil, i,
281
+ :digital_hour, :simple_hour, :named_hour, :numeral_hour,
282
+ :named_digits)
173
283
  end
174
- def simple_hour(i)
175
- seq(:simple_hour, i, :_simple_hour, :am_pm, '?')
284
+
285
+ def weekday(i)
286
+ rex(:weekday, i, /(#{WEEKDAYS.reverse.join('|')})\s*/i)
176
287
  end
177
288
 
178
- def _numeral_hour(i)
179
- rex(:nh, i, /(#{NUMS.join('|')})/i)
289
+ def and_at(i)
290
+ seq(nil, i, :_and_or_comma, :at_point)
180
291
  end
181
- def numeral_hour(i)
182
- seq(:numeral_hour, i, :_numeral_hour, :am_pm, '?')
292
+
293
+ def _intervals(i)
294
+ rex(:intervals, i,
295
+ /(
296
+ y(ears?)?|months?|w(eeks?)?|d(ays?)?|
297
+ h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?
298
+ )(\s+|$)/ix)
183
299
  end
184
300
 
185
- def name_hour(i)
186
- rex(:name_hour, i, /(#{NHOURS.keys.join('|')})/i)
301
+ def sinterval(i)
302
+ rex(:sinterval, i,
303
+ /(year|month|week|day|hour|min(ute)?|sec(ond)?)(\s+|$)/i)
304
+ end
305
+ def ninterval(i)
306
+ seq(:ninterval, i, :integer, :_intervals)
187
307
  end
188
308
 
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
309
+ def ordinal(i)
310
+ rex(:ordinal, i, /\s*(#{ORDINALS.keys.join('|')})\s*/)
311
+ end
192
312
 
193
- def range_sep(i); rex(nil, i, / *- *| +(to|through) +/); end
313
+ def _mod(i); rex(nil, i, /\s*month\s+on\s+days?\s+/i); end
314
+ def _oftm(i); rex(nil, i, /\s*(day\s)?\s*of\s+the\s+month\s*/i); end
194
315
 
195
- def day_range(i)
196
- seq(:day_range, i, :name_day, :range_sep, :name_day)
316
+ def dom(i)
317
+ rex(:int, i, /([12][0-9]|3[01]|[0-9])/)
318
+ end
319
+ def and_or_dom(i)
320
+ seq(nil, i, :_and_or_comma, :dom)
321
+ end
322
+ def dom_list(i)
323
+ seq(:dom_list, i, :dom, :and_or_dom, '*')
197
324
  end
198
325
 
199
- def _tz_name(i)
200
- rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/)
326
+ def dom_mod(i) # every month on day
327
+ seq(:dom, i, :_mod, :dom_list)
328
+ end
329
+ def dom_noftm(i) # every nth of month
330
+ seq(:dom, i, :ordinal, :_oftm)
201
331
  end
202
- def _tz_delta(i)
203
- rex(nil, i, /[-+]([01][0-9]|2[0-4]):?(00|15|30|45)/)
332
+ def day_of_month(i)
333
+ alt(nil, i, :dom_noftm, :dom_mod)
204
334
  end
205
- def _tz(i); alt(:tz, i, :_tz_delta, :_tz_name); end
206
335
 
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)
336
+ def dow_class(i)
337
+ rex(:dow_class, i, /(weekday)(\s+|$)/i)
215
338
  end
216
339
 
217
- def flag(i); rex(:flag, i, /(every|from|at|after|on|in)/i); end
340
+ def dow(i)
341
+ seq(:dow, i, :weekday)
342
+ end
343
+ def and_or_dow(i)
344
+ seq(nil, i, :_and_or_comma, :dow)
345
+ end
346
+ def dow_list(i)
347
+ seq(:dow_list, i, :dow, :and_or_dow, '*')
348
+ end
218
349
 
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)
350
+ def to_dow_range(i)
351
+ seq(:dow_range, i, :weekday, :_to_through, :weekday)
352
+ end
353
+ def dash_dow_range(i)
354
+ seq(:dow_range, i, :weekday, :_dash, :weekday)
355
+ end
356
+ def dow_range(i)
357
+ alt(nil, i, :to_dow_range, :dash_dow_range)
358
+ end
359
+
360
+ def day_of_week(i)
361
+ alt(nil, i, :dow_range, :dow_list, :dow_class)
362
+ end
363
+
364
+ def interval(i)
365
+ alt(nil, i, :sinterval, :ninterval)
366
+ end
367
+
368
+ def every_object(i)
369
+ alt(nil, i, :day_of_month, :interval, :day_of_week)
370
+ end
371
+ def from_object(i)
372
+ alt(nil, i, :interval, :to_dow_range)
373
+ end
374
+
375
+ def tz(i)
376
+ seq(nil, i, :_in, '?', :tzone)
377
+ end
378
+ def on(i)
379
+ seq(:on, i, :_on, :weekday, :at_point, :and_at, '*')
380
+ end
381
+ def at(i)
382
+ seq(:at, i, :_at_comma, :at_point, :and_at, '*')
383
+ end
384
+ def from(i)
385
+ seq(:from, i, :_from, :from_object)
386
+ end
387
+ def every(i)
388
+ seq(:every, i, :_every, :every_object)
389
+ end
390
+
391
+ def at_from(i)
392
+ seq(nil, i, :at, :from, :tz, '?')
393
+ end
394
+ def at_every(i)
395
+ seq(nil, i, :at, :every, :tz, '?')
396
+ end
397
+
398
+ def from_at(i)
399
+ seq(nil, i, :from, :at, '?', :tz, '?')
227
400
  end
228
401
 
229
- def sugar(i); rex(nil, i, /(and|or|[, \t]+)/i); end
402
+ def every_(i)
403
+ seq(nil, i, :every, :tz, '?')
404
+ end
405
+ def every_on(i)
406
+ seq(nil, i, :every, :on, :tz, '?')
407
+ end
408
+ def every_at(i)
409
+ seq(nil, i, :every, :at, :tz, '?')
410
+ end
230
411
 
231
- def elt(i); alt(nil, i, :sugar, :datum); end
232
- def nat(i); rep(:nat, i, :elt, 1); end
412
+ def nat(i)
413
+ alt(:nat, i,
414
+ :every_at, :every_on, :every_,
415
+ :from_at,
416
+ :at_every, :at_from)
417
+ end
233
418
 
234
419
  # rewrite parsed tree
235
420
 
236
- def rewrite_nat(t)
421
+ #def _rewrite_single(t)
422
+ # [ t.name, rewrite(t.sublookup(nil)) ]
423
+ #end
424
+ def _rewrite_children(t)
425
+ t.subgather(nil).collect { |tt| rewrite(tt) }
426
+ end
427
+ def _rewrite_multiple(t)
428
+ [ t.name, _rewrite_children(t) ]
429
+ end
430
+ def _rewrite_child(t)
431
+ rewrite(t.sublookup(nil))
432
+ end
433
+
434
+ def rewrite_int(t); t.string.to_i; end
435
+
436
+ def rewrite_tzone(t)
437
+
438
+ [ :tz, t.strim ]
439
+ end
440
+
441
+ def rewrite_sinterval(t)
442
+
443
+ [ :interval, 1, t.strim ]
444
+ end
445
+
446
+ def rewrite_ninterval(t)
447
+
448
+ [ :interval,
449
+ t.sublookup(:int).string.to_i,
450
+ t.sublookup(:intervals).strim ]
451
+ end
452
+
453
+ def rewrite_named_digit(t)
454
+
455
+ i = t.sublookup(:int).string.to_i
456
+
457
+ case n = t.sublookup(:dname).strim
458
+ when /^s/ then [ '*', '*', i ]
459
+ when /^m/ then [ '*', i ]
460
+ end
461
+ end
462
+
463
+ def rewrite_named_hour(t)
464
+ NHOURS[t.strim.downcase]
465
+ end
466
+ def rewrite_numeral_hour(t)
467
+ vs = t.subgather(nil).collect { |st| st.strim.downcase }
468
+ v = NUMS.index(vs[0])
469
+ v += 12 if vs[1] == 'pm'
470
+ m = NMINUTES[vs[2]] || 0
471
+ [ v, m ]
472
+ end
473
+ def rewrite_simple_hour(t)
474
+ vs = t.subgather(nil).collect { |st| st.strim.downcase }
475
+ v = vs[0].to_i
476
+ v += 12 if vs[1] == 'pm'
477
+ [ v, 0 ]
478
+ end
479
+ def rewrite_digital_hour(t)
480
+ m = t.string.match(/(\d\d?):?(\d\d)/)
481
+ [ m[1].to_i, m[2].to_i ]
482
+ end
483
+
484
+ def rewrite_weekday(t)
485
+
486
+ WEEKDAYS.index(t.strim.downcase[0, 3])
487
+ end
488
+
489
+ def rewrite_ordinal(t); ORDINALS[t.strim]; end
490
+
491
+ def rewrite_dom(t)
237
492
 
238
493
  #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 }
494
+ [ :day_of_month,
495
+ *_rewrite_children(t).flatten.select { |e| e.is_a?(Integer) } ]
496
+ end
497
+
498
+ alias rewrite_dow _rewrite_child
499
+
500
+ def rewrite_dom_list(t); [ :dom_list, *_rewrite_children(t) ]; end
501
+ def rewrite_dow_list(t); [ :dow_list, *_rewrite_children(t) ]; end
502
+
503
+ def rewrite_dow_class(t)
504
+
505
+ [ :dow_range, 1, 5 ] # only "weekday" for now
506
+ end
507
+
508
+ def rewrite_dow_range(t)
509
+
510
+ tts = t.subgather(nil)
511
+
512
+ [ :dow_range, rewrite(tts[0]), rewrite(tts[1]) ]
513
+ end
514
+
515
+ alias rewrite_on _rewrite_multiple
516
+ alias rewrite_at _rewrite_multiple
517
+
518
+ alias rewrite_from _rewrite_child
519
+ alias rewrite_every _rewrite_child
520
+
521
+ def rewrite_nat(t)
522
+
523
+ t.subgather(nil).collect { |tt| rewrite(tt) }
524
+ #.tap { |x| pp x }
268
525
  end
269
526
  end
270
527
  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.2
4
+ version: 1.3.7
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-14 00:00:00.000000000 Z
11
+ date: 2020-08-05 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