sof-cycle 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -2
- data/README.md +6 -1
- data/Rakefile +1 -1
- data/checksums/sof-cycle-0.1.0.gem.sha512 +1 -0
- data/checksums/sof-cycle-0.1.1.gem.sha512 +1 -0
- data/checksums/sof-cycle-0.1.2.gem.sha512 +1 -0
- data/lib/sof/cycle/version.rb +1 -1
- data/lib/sof/cycle.rb +11 -152
- data/lib/sof/cycles/calendar.rb +42 -0
- data/lib/sof/cycles/dormant.rb +38 -0
- data/lib/sof/cycles/end_of.rb +39 -0
- data/lib/sof/cycles/lookback.rb +43 -0
- data/lib/sof/cycles/volume_only.rb +31 -0
- data/lib/sof/cycles/within.rb +26 -0
- data/lib/sof/{cycle/parser.rb → parser.rb} +8 -8
- data/lib/sof/{cycle/time_span.rb → time_span.rb} +1 -7
- data/lib/sof-cycle.rb +16 -0
- metadata +14 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea1c2c9a48bba72114b7681278cd467c57ee147f6f8b1ff176b4508de355920b
|
|
4
|
+
data.tar.gz: 93a7560799df4730a5eaf2d14708b9a24199c0a95d2b01f1487ddc587b0846da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: baf569f5fb7e0500b8a28915c8f19b86dc97ebee380301787182216d162fd02e659645345d369440959ebde737b59bb8ddd4372dae0bf014a9e3b448e8455e03
|
|
7
|
+
data.tar.gz: 84756e0428c56a7f6dd72e5dc7aa545a587bbffd49479a436e4f8bee2d60ec3496e128f713a36b0169799ff908e0690e4518b147e750eaa0fb662681f0e036a0
|
data/CHANGELOG.md
CHANGED
|
@@ -5,8 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.1.
|
|
8
|
+
## [0.1.2] - 2024-08-09
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- `Cycle#recurring?` to reveal if a given Cycle is one-and-done or must be repeated.
|
|
13
|
+
|
|
14
|
+
## [0.1.1] - 2024-08-09
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Basic example in README.md
|
|
19
|
+
- Add `Cycles::EndOf` to handle cycles that cover through the end of the nth
|
|
20
|
+
subsequent period
|
|
21
|
+
- Add predicate methods for each `Cycle` subclass. E.g. `#dormant?`, `#within?`, etc
|
|
22
|
+
- Refactor into namespaces
|
data/README.md
CHANGED
|
@@ -14,7 +14,12 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
```ruby
|
|
18
|
+
cycle = SOF::Cycle.load({ volume: 3, kind: :lookback, period: :day, period_count: 3 })
|
|
19
|
+
cycle.to_h # => { volume: 3, kind: :lookback, period: :day, period_count: 3 }
|
|
20
|
+
cycle.notation # => "V3L3D"
|
|
21
|
+
cycle.to_s # => "3x in the prior 3 days"
|
|
22
|
+
```
|
|
18
23
|
|
|
19
24
|
## Development
|
|
20
25
|
|
data/Rakefile
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
9a87a33268363c7f7afbb084677ff7e37c9b1102992b8082cac7529cfb3871fc92e8218d14de218a04b26afb415505286a76f948094b3cd5032b52dcf81645c6
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
53766201b732ae80e67079d1efeda25c86f144cc59c1aed2a210a8d068a689409bfd1fc0e6be7b769ae15500c3224d2c5c7854385ac84b60e44a54f94bb22c65
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
78a488a4cea134800aeca3e3e06461a413c564b111d96b25a74c4ae44b5afa66a04d4c59be33cc455bab0c9b1d80cfdd3af1b980859ae3955cb1fd7350fe1459
|
data/lib/sof/cycle/version.rb
CHANGED
data/lib/sof/cycle.rb
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
4
|
-
require "forwardable"
|
|
5
|
-
require_relative "cycle/parser"
|
|
6
|
-
require_relative "cycle/time_span"
|
|
7
|
-
require "active_support/core_ext/date/conversions"
|
|
8
|
-
require "active_support/core_ext/string/filters"
|
|
3
|
+
require_relative "parser"
|
|
9
4
|
|
|
10
5
|
module SOF
|
|
11
6
|
class Cycle
|
|
@@ -30,8 +25,10 @@ module SOF
|
|
|
30
25
|
|
|
31
26
|
delegate [:activated_notation, :volume, :from, :from_date, :time_span, :period,
|
|
32
27
|
:humanized_period, :period_key, :active?] => :@parser
|
|
33
|
-
delegate [:kind, :volume_only?, :valid_periods] => "self.class"
|
|
28
|
+
delegate [:kind, :recurring?, :volume_only?, :valid_periods] => "self.class"
|
|
34
29
|
delegate [:period_count, :duration] => :time_span
|
|
30
|
+
delegate [:calendar?, :dormant?, :end_of?, :lookback?, :volume_only?,
|
|
31
|
+
:within?] => :kind_inquiry
|
|
35
32
|
|
|
36
33
|
# Turn a cycle or notation string into a hash
|
|
37
34
|
def self.dump(cycle_or_string)
|
|
@@ -83,7 +80,7 @@ module SOF
|
|
|
83
80
|
# @return [Cycle] a Cycle object representing the provide string notation
|
|
84
81
|
def self.for(notation)
|
|
85
82
|
return notation if notation.is_a? Cycle
|
|
86
|
-
return notation if notation.is_a?
|
|
83
|
+
return notation if notation.is_a? Cycles::Dormant
|
|
87
84
|
parser = Parser.new(notation)
|
|
88
85
|
unless parser.valid?
|
|
89
86
|
raise InvalidInput, "'#{notation}' is not a valid input"
|
|
@@ -94,7 +91,7 @@ module SOF
|
|
|
94
91
|
end.new(notation, parser:)
|
|
95
92
|
return cycle if parser.active?
|
|
96
93
|
|
|
97
|
-
|
|
94
|
+
Cycles::Dormant.new(cycle, parser:)
|
|
98
95
|
end
|
|
99
96
|
|
|
100
97
|
# Return the appropriate class for the give notation id
|
|
@@ -133,10 +130,11 @@ module SOF
|
|
|
133
130
|
@kind = nil
|
|
134
131
|
@valid_periods = []
|
|
135
132
|
|
|
136
|
-
def self.volume_only? = @volume_only
|
|
137
|
-
|
|
138
133
|
class << self
|
|
139
134
|
attr_reader :notation_id, :kind, :valid_periods
|
|
135
|
+
def volume_only? = @volume_only
|
|
136
|
+
|
|
137
|
+
def recurring? = raise "#{name} must implement #{__method__}"
|
|
140
138
|
end
|
|
141
139
|
|
|
142
140
|
# Raises an error if the given period isn't in the list of valid periods.
|
|
@@ -150,6 +148,8 @@ module SOF
|
|
|
150
148
|
ERR
|
|
151
149
|
end
|
|
152
150
|
|
|
151
|
+
def kind_inquiry = ActiveSupport::StringInquirer.new(kind.to_s)
|
|
152
|
+
|
|
153
153
|
def validate_period
|
|
154
154
|
return if valid_periods.empty?
|
|
155
155
|
|
|
@@ -208,146 +208,5 @@ module SOF
|
|
|
208
208
|
end
|
|
209
209
|
|
|
210
210
|
def as_json(...) = notation
|
|
211
|
-
|
|
212
|
-
class Dormant
|
|
213
|
-
def initialize(cycle, parser:)
|
|
214
|
-
@cycle = cycle
|
|
215
|
-
@parser = parser
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
attr_reader :cycle, :parser
|
|
219
|
-
|
|
220
|
-
def to_s
|
|
221
|
-
cycle.to_s + " (dormant)"
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def covered_dates(...) = []
|
|
225
|
-
|
|
226
|
-
def expiration_of(...) = nil
|
|
227
|
-
|
|
228
|
-
def satisfied_by?(...) = false
|
|
229
|
-
|
|
230
|
-
def cover?(...) = false
|
|
231
|
-
|
|
232
|
-
def method_missing(method, ...) = cycle.send(method, ...)
|
|
233
|
-
|
|
234
|
-
def respond_to_missing?(method, include_private = false)
|
|
235
|
-
cycle.respond_to?(method, include_private)
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
class Within < self
|
|
240
|
-
@volume_only = false
|
|
241
|
-
@notation_id = "W"
|
|
242
|
-
@kind = :within
|
|
243
|
-
@valid_periods = %w[D W M Y]
|
|
244
|
-
|
|
245
|
-
def to_s = "#{volume}x within #{date_range}"
|
|
246
|
-
|
|
247
|
-
def date_range
|
|
248
|
-
return humanized_span unless active?
|
|
249
|
-
|
|
250
|
-
[start_date, final_date].map { _1.to_fs(:american) }.join(" - ")
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def final_date(_ = nil) = time_span.end_date(start_date)
|
|
254
|
-
|
|
255
|
-
def start_date(_ = nil) = from_date.to_date
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
class VolumeOnly < self
|
|
259
|
-
@volume_only = true
|
|
260
|
-
@notation_id = nil
|
|
261
|
-
@kind = :volume_only
|
|
262
|
-
@valid_periods = []
|
|
263
|
-
|
|
264
|
-
class << self
|
|
265
|
-
def handles?(sym) = sym.nil? || super
|
|
266
|
-
|
|
267
|
-
def validate_period(period)
|
|
268
|
-
raise InvalidPeriod, <<~ERR.squish unless period.nil?
|
|
269
|
-
Invalid period value of '#{period}' provided. Valid periods are:
|
|
270
|
-
#{valid_periods.join(", ")}
|
|
271
|
-
ERR
|
|
272
|
-
end
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
def to_s = "#{volume}x total"
|
|
276
|
-
|
|
277
|
-
def covered_dates(dates, ...) = dates
|
|
278
|
-
|
|
279
|
-
def cover?(...) = true
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
class Lookback < self
|
|
283
|
-
@volume_only = false
|
|
284
|
-
@notation_id = "L"
|
|
285
|
-
@kind = :lookback
|
|
286
|
-
@valid_periods = %w[D W M Y]
|
|
287
|
-
|
|
288
|
-
def to_s = "#{volume}x in the prior #{period_count} #{humanized_period}"
|
|
289
|
-
|
|
290
|
-
def volume_to_delay_expiration(completion_dates, anchor:)
|
|
291
|
-
oldest_relevant_completion = completion_dates.min
|
|
292
|
-
[completion_dates.count(oldest_relevant_completion), volume].min
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
# "Absent further completions, you go red on this date"
|
|
296
|
-
# @return [Date, nil] the date on which the cycle will expire given the
|
|
297
|
-
# provided completion dates. Returns nil if the cycle is already unsatisfied.
|
|
298
|
-
def expiration_of(completion_dates)
|
|
299
|
-
anchor = completion_dates.max_by(volume) { _1 }.min
|
|
300
|
-
return unless satisfied_by?(completion_dates, anchor:)
|
|
301
|
-
|
|
302
|
-
window_end anchor
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def final_date(anchor)
|
|
306
|
-
return if anchor.nil?
|
|
307
|
-
|
|
308
|
-
time_span.end_date(anchor.to_date)
|
|
309
|
-
end
|
|
310
|
-
alias_method :window_end, :final_date
|
|
311
|
-
|
|
312
|
-
def start_date(anchor)
|
|
313
|
-
time_span.begin_date(anchor.to_date)
|
|
314
|
-
end
|
|
315
|
-
alias_method :window_start, :start_date
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
class Calendar < self
|
|
319
|
-
@volume_only = false
|
|
320
|
-
@notation_id = "C"
|
|
321
|
-
@kind = :calendar
|
|
322
|
-
@valid_periods = %w[M Q Y]
|
|
323
|
-
|
|
324
|
-
class << self
|
|
325
|
-
def frame_of_reference = "total"
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def to_s
|
|
329
|
-
"#{volume}x every #{period_count} calendar #{humanized_period}"
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
# "Absent further completions, you go red on this date"
|
|
333
|
-
# @return [Date, nil] the date on which the cycle will expire given the
|
|
334
|
-
# provided completion dates. Returns nil if the cycle is already unsatisfied.
|
|
335
|
-
def expiration_of(completion_dates)
|
|
336
|
-
anchor = completion_dates.max_by(volume) { _1 }.min
|
|
337
|
-
return unless satisfied_by?(completion_dates, anchor:)
|
|
338
|
-
|
|
339
|
-
window_end(anchor) + duration
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
def final_date(anchor)
|
|
343
|
-
return if anchor.nil?
|
|
344
|
-
time_span.end_date_of_period(anchor.to_date)
|
|
345
|
-
end
|
|
346
|
-
alias_method :window_end, :final_date
|
|
347
|
-
|
|
348
|
-
def start_date(anchor)
|
|
349
|
-
time_span.begin_date_of_period(anchor.to_date)
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
211
|
end
|
|
353
212
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SOF
|
|
4
|
+
module Cycles
|
|
5
|
+
class Calendar < Cycle
|
|
6
|
+
@volume_only = false
|
|
7
|
+
@notation_id = "C"
|
|
8
|
+
@kind = :calendar
|
|
9
|
+
@valid_periods = %w[M Q Y]
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def frame_of_reference = "total"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.recurring? = true
|
|
16
|
+
|
|
17
|
+
def to_s
|
|
18
|
+
"#{volume}x every #{period_count} calendar #{humanized_period}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# "Absent further completions, you go red on this date"
|
|
22
|
+
# @return [Date, nil] the date on which the cycle will expire given the
|
|
23
|
+
# provided completion dates. Returns nil if the cycle is already unsatisfied.
|
|
24
|
+
def expiration_of(completion_dates)
|
|
25
|
+
anchor = completion_dates.max_by(volume) { _1 }.min
|
|
26
|
+
return unless satisfied_by?(completion_dates, anchor:)
|
|
27
|
+
|
|
28
|
+
window_end(anchor) + duration
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def final_date(anchor)
|
|
32
|
+
return if anchor.nil?
|
|
33
|
+
time_span.end_date_of_period(anchor.to_date)
|
|
34
|
+
end
|
|
35
|
+
alias_method :window_end, :final_date
|
|
36
|
+
|
|
37
|
+
def start_date(anchor)
|
|
38
|
+
time_span.begin_date_of_period(anchor.to_date)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SOF
|
|
4
|
+
module Cycles
|
|
5
|
+
class Dormant
|
|
6
|
+
def initialize(cycle, parser:)
|
|
7
|
+
@cycle = cycle
|
|
8
|
+
@parser = parser
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :cycle, :parser
|
|
12
|
+
|
|
13
|
+
def self.recurring? = false
|
|
14
|
+
|
|
15
|
+
def kind = :dormant
|
|
16
|
+
|
|
17
|
+
def dormant? = true
|
|
18
|
+
|
|
19
|
+
def to_s
|
|
20
|
+
cycle.to_s + " (dormant)"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def covered_dates(...) = []
|
|
24
|
+
|
|
25
|
+
def expiration_of(...) = nil
|
|
26
|
+
|
|
27
|
+
def satisfied_by?(...) = false
|
|
28
|
+
|
|
29
|
+
def cover?(...) = false
|
|
30
|
+
|
|
31
|
+
def method_missing(method, ...) = cycle.send(method, ...)
|
|
32
|
+
|
|
33
|
+
def respond_to_missing?(method, include_private = false)
|
|
34
|
+
cycle.respond_to?(method, include_private)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SOF
|
|
4
|
+
module Cycles
|
|
5
|
+
class EndOf < Cycle
|
|
6
|
+
@volume_only = false
|
|
7
|
+
@notation_id = "E"
|
|
8
|
+
@kind = :end_of
|
|
9
|
+
@valid_periods = %w[W M Q Y]
|
|
10
|
+
|
|
11
|
+
def self.recurring? = true
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
return dormant_to_s if dormant?
|
|
15
|
+
|
|
16
|
+
"#{volume}x by #{final_date.to_fs(:american)}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def final_date(_ = nil) = time_span
|
|
20
|
+
.end_date(start_date)
|
|
21
|
+
.end_of_month
|
|
22
|
+
|
|
23
|
+
def start_date(_ = nil) = from_date.to_date
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def dormant_to_s
|
|
28
|
+
<<~DESC.squish
|
|
29
|
+
#{volume}x by the last day of the #{subsequent_ordinal}
|
|
30
|
+
subsequent #{period}
|
|
31
|
+
DESC
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def subsequent_ordinal
|
|
35
|
+
ActiveSupport::Inflector.ordinalize(period_count - 1)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SOF
|
|
4
|
+
module Cycles
|
|
5
|
+
class Lookback < Cycle
|
|
6
|
+
@volume_only = false
|
|
7
|
+
@notation_id = "L"
|
|
8
|
+
@kind = :lookback
|
|
9
|
+
@valid_periods = %w[D W M Y]
|
|
10
|
+
|
|
11
|
+
def self.recurring? = true
|
|
12
|
+
|
|
13
|
+
def to_s = "#{volume}x in the prior #{period_count} #{humanized_period}"
|
|
14
|
+
|
|
15
|
+
def volume_to_delay_expiration(completion_dates, anchor:)
|
|
16
|
+
oldest_relevant_completion = completion_dates.min
|
|
17
|
+
[completion_dates.count(oldest_relevant_completion), volume].min
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# "Absent further completions, you go red on this date"
|
|
21
|
+
# @return [Date, nil] the date on which the cycle will expire given the
|
|
22
|
+
# provided completion dates. Returns nil if the cycle is already unsatisfied.
|
|
23
|
+
def expiration_of(completion_dates)
|
|
24
|
+
anchor = completion_dates.max_by(volume) { _1 }.min
|
|
25
|
+
return unless satisfied_by?(completion_dates, anchor:)
|
|
26
|
+
|
|
27
|
+
window_end anchor
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def final_date(anchor)
|
|
31
|
+
return if anchor.nil?
|
|
32
|
+
|
|
33
|
+
time_span.end_date(anchor.to_date)
|
|
34
|
+
end
|
|
35
|
+
alias_method :window_end, :final_date
|
|
36
|
+
|
|
37
|
+
def start_date(anchor)
|
|
38
|
+
time_span.begin_date(anchor.to_date)
|
|
39
|
+
end
|
|
40
|
+
alias_method :window_start, :start_date
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SOF
|
|
4
|
+
module Cycles
|
|
5
|
+
class VolumeOnly < Cycle
|
|
6
|
+
@volume_only = true
|
|
7
|
+
@notation_id = nil
|
|
8
|
+
@kind = :volume_only
|
|
9
|
+
@valid_periods = []
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def handles?(sym) = sym.nil? || super
|
|
13
|
+
|
|
14
|
+
def validate_period(period)
|
|
15
|
+
raise InvalidPeriod, <<~ERR.squish unless period.nil?
|
|
16
|
+
Invalid period value of '#{period}' provided. Valid periods are:
|
|
17
|
+
#{valid_periods.join(", ")}
|
|
18
|
+
ERR
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.recurring? = false
|
|
23
|
+
|
|
24
|
+
def to_s = "#{volume}x total"
|
|
25
|
+
|
|
26
|
+
def covered_dates(dates, ...) = dates
|
|
27
|
+
|
|
28
|
+
def cover?(...) = true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SOF
|
|
4
|
+
module Cycles
|
|
5
|
+
class Within < Cycle
|
|
6
|
+
@volume_only = false
|
|
7
|
+
@notation_id = "W"
|
|
8
|
+
@kind = :within
|
|
9
|
+
@valid_periods = %w[D W M Y]
|
|
10
|
+
|
|
11
|
+
def self.recurring? = false
|
|
12
|
+
|
|
13
|
+
def to_s = "#{volume}x within #{date_range}"
|
|
14
|
+
|
|
15
|
+
def date_range
|
|
16
|
+
return humanized_span unless active?
|
|
17
|
+
|
|
18
|
+
[start_date, final_date].map { _1.to_fs(:american) }.join(" - ")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def final_date(_ = nil) = time_span.end_date(start_date)
|
|
22
|
+
|
|
23
|
+
def start_date(_ = nil) = from_date.to_date
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "cycle"
|
|
4
4
|
require "active_support/core_ext/hash/keys"
|
|
5
5
|
require "active_support/core_ext/object/blank"
|
|
6
6
|
require "active_support/core_ext/object/inclusion"
|
|
@@ -10,22 +10,22 @@ require "active_support/isolated_execution_state"
|
|
|
10
10
|
module SOF
|
|
11
11
|
# This class is not intended to be referenced directly.
|
|
12
12
|
# This is an internal implementation of Cycle behavior.
|
|
13
|
-
class
|
|
13
|
+
class Parser
|
|
14
14
|
extend Forwardable
|
|
15
15
|
PARTS_REGEX = /
|
|
16
16
|
^(?<vol>V(?<volume>\d*))? # optional volume
|
|
17
|
-
(?<set>(?<kind>L|C|W) # kind
|
|
17
|
+
(?<set>(?<kind>L|C|W|E) # kind
|
|
18
18
|
(?<period_count>\d+) # period count
|
|
19
19
|
(?<period_key>D|W|M|Q|Y)?)? # period_key
|
|
20
20
|
(?<from>F(?<from_date>\d{4}-\d{2}-\d{2}))?$ # optional from
|
|
21
21
|
/ix
|
|
22
22
|
|
|
23
|
-
def self.dormant_capable_kinds = %w[W]
|
|
23
|
+
def self.dormant_capable_kinds = %w[E W]
|
|
24
24
|
|
|
25
|
-
def self.for(
|
|
26
|
-
return
|
|
25
|
+
def self.for(notation_or_parser)
|
|
26
|
+
return notation_or_parser if notation_or_parser.is_a? self
|
|
27
27
|
|
|
28
|
-
new(
|
|
28
|
+
new(notation_or_parser)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def self.load(hash)
|
|
@@ -50,7 +50,7 @@ module SOF
|
|
|
50
50
|
|
|
51
51
|
# Return a TimeSpan object for the period and period_count
|
|
52
52
|
def time_span
|
|
53
|
-
@time_span ||=
|
|
53
|
+
@time_span ||= TimeSpan.for(period_count, period_key)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def valid? = match.present?
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../cycle"
|
|
4
|
-
require "active_support/deprecator"
|
|
5
|
-
require "active_support/deprecation"
|
|
6
|
-
require "active_support/core_ext/numeric/time"
|
|
7
|
-
require "active_support/core_ext/integer/time"
|
|
8
|
-
require "active_support/core_ext/string/conversions"
|
|
9
3
|
module SOF
|
|
10
4
|
# This class is not intended to be referenced directly.
|
|
11
5
|
# This is an internal implementation of Cycle behavior.
|
|
12
|
-
class
|
|
6
|
+
class TimeSpan
|
|
13
7
|
extend Forwardable
|
|
14
8
|
# TimeSpan objects map Cycle notations to behaviors for their periods
|
|
15
9
|
#
|
data/lib/sof-cycle.rb
CHANGED
|
@@ -1 +1,17 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
require "active_support/deprecator"
|
|
3
|
+
require "active_support/deprecation"
|
|
4
|
+
require "active_support/core_ext/numeric/time"
|
|
5
|
+
require "active_support/core_ext/integer/time"
|
|
6
|
+
require "active_support/core_ext/string/conversions"
|
|
7
|
+
require "active_support/core_ext/date/conversions"
|
|
8
|
+
require "active_support/core_ext/string/filters"
|
|
9
|
+
require "active_support/inflector"
|
|
10
|
+
require "active_support/string_inquirer"
|
|
11
|
+
|
|
12
|
+
require_relative "sof/cycle/version"
|
|
1
13
|
require_relative "sof/cycle"
|
|
14
|
+
|
|
15
|
+
Dir[File.join(__dir__, "sof", "cycles", "*.rb")].each { |file| require file }
|
|
16
|
+
|
|
17
|
+
require_relative "sof/time_span"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sof-cycle
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jim Gay
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-
|
|
11
|
+
date: 2024-08-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: forwardable
|
|
@@ -50,11 +50,20 @@ files:
|
|
|
50
50
|
- CHANGELOG.md
|
|
51
51
|
- README.md
|
|
52
52
|
- Rakefile
|
|
53
|
+
- checksums/sof-cycle-0.1.0.gem.sha512
|
|
54
|
+
- checksums/sof-cycle-0.1.1.gem.sha512
|
|
55
|
+
- checksums/sof-cycle-0.1.2.gem.sha512
|
|
53
56
|
- lib/sof-cycle.rb
|
|
54
57
|
- lib/sof/cycle.rb
|
|
55
|
-
- lib/sof/cycle/parser.rb
|
|
56
|
-
- lib/sof/cycle/time_span.rb
|
|
57
58
|
- lib/sof/cycle/version.rb
|
|
59
|
+
- lib/sof/cycles/calendar.rb
|
|
60
|
+
- lib/sof/cycles/dormant.rb
|
|
61
|
+
- lib/sof/cycles/end_of.rb
|
|
62
|
+
- lib/sof/cycles/lookback.rb
|
|
63
|
+
- lib/sof/cycles/volume_only.rb
|
|
64
|
+
- lib/sof/cycles/within.rb
|
|
65
|
+
- lib/sof/parser.rb
|
|
66
|
+
- lib/sof/time_span.rb
|
|
58
67
|
homepage: https://github.com/SOFware/sof-cycle
|
|
59
68
|
licenses: []
|
|
60
69
|
metadata:
|
|
@@ -75,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
75
84
|
- !ruby/object:Gem::Version
|
|
76
85
|
version: '0'
|
|
77
86
|
requirements: []
|
|
78
|
-
rubygems_version: 3.
|
|
87
|
+
rubygems_version: 3.4.13
|
|
79
88
|
signing_key:
|
|
80
89
|
specification_version: 4
|
|
81
90
|
summary: Parse and interact with SOF cycle notation.
|