Class: SOF::Cycle
- Inherits:
-
Object
- Object
- SOF::Cycle
- Extended by:
- Forwardable
- Defined in:
- lib/sof/cycle.rb,
lib/sof/cycle/version.rb
Direct Known Subclasses
SOF::Cycles::Calendar, SOF::Cycles::EndOf, SOF::Cycles::Lookback, SOF::Cycles::VolumeOnly, SOF::Cycles::Within
Defined Under Namespace
Classes: InvalidInput, InvalidKind, InvalidPeriod
Constant Summary collapse
- VERSION =
"0.1.12"
Class Attribute Summary collapse
-
.kind ⇒ Object
readonly
Returns the value of attribute kind.
-
.notation_id ⇒ Object
readonly
Returns the value of attribute notation_id.
-
.valid_periods ⇒ Object
readonly
Returns the value of attribute valid_periods.
Instance Attribute Summary collapse
-
#parser ⇒ Object
readonly
Returns the value of attribute parser.
Class Method Summary collapse
-
.class_for_kind(sym) ⇒ Object
Return the class handling the kind.
-
.class_for_notation_id(notation_id) ⇒ Object
Return the appropriate class for the give notation id.
- .cycle_handlers ⇒ Object
-
.dump(cycle_or_string) ⇒ Object
Turn a cycle or notation string into a hash.
-
.for(notation) ⇒ Cycle
Return a Cycle object from a notation string.
- .handles?(sym) ⇒ Boolean
- .inherited(klass) ⇒ Object
-
.legend ⇒ Hash
Return a legend explaining all notation components.
-
.load(hash) ⇒ Object
Return a Cycle object from a hash.
-
.notation(hash) ⇒ String
Retun a notation string from a hash.
- .recurring? ⇒ Boolean
-
.validate_period(period) ⇒ Object
Raises an error if the given period isn’t in the list of valid periods.
- .volume_only? ⇒ Boolean
Instance Method Summary collapse
-
#==(other) ⇒ Object
Cycles are considered equal if their hash representations are equal.
- #as_json ⇒ Object
- #considered_dates(completion_dates, anchor: Date.current) ⇒ Object
- #cover?(date, anchor: Date.current) ⇒ Boolean
- #covered_dates(dates, anchor: Date.current) ⇒ Object
- #expiration_of(_completion_dates, anchor: Date.current) ⇒ Object
- #extend_period(_ = nil) ⇒ Object
-
#final_date(_anchor) ⇒ Object
Return the final date of the cycle.
- #from_data ⇒ Object
- #humanized_span ⇒ Object
-
#initialize(notation, parser: Parser.new(notation)) ⇒ Cycle
constructor
A new instance of Cycle.
- #kind_inquiry ⇒ Object
-
#last_completed(dates) ⇒ Object
Return the most recent completion date from the supplied array of dates.
-
#notation ⇒ Object
Return the cycle representation as a notation string.
- #range(anchor) ⇒ Object
-
#satisfied_by?(completion_dates, anchor: Date.current) ⇒ Boolean
From the supplied anchor date, are there enough in-window completions to satisfy the cycle?.
- #to_h ⇒ Object
- #validate_period ⇒ Object
- #volume_to_delay_expiration(_completion_dates, anchor:) ⇒ Object
Constructor Details
#initialize(notation, parser: Parser.new(notation)) ⇒ Cycle
Returns a new instance of Cycle.
206 207 208 209 210 211 212 213 214 |
# File 'lib/sof/cycle.rb', line 206 def initialize(notation, parser: Parser.new(notation)) @notation = notation @parser = parser validate_period return if @parser.valid? raise InvalidInput, "'#{notation}' is not a valid input" end |
Class Attribute Details
.kind ⇒ Object (readonly)
Returns the value of attribute kind.
130 131 132 |
# File 'lib/sof/cycle.rb', line 130 def kind @kind end |
.notation_id ⇒ Object (readonly)
Returns the value of attribute notation_id.
130 131 132 |
# File 'lib/sof/cycle.rb', line 130 def notation_id @notation_id end |
.valid_periods ⇒ Object (readonly)
Returns the value of attribute valid_periods.
130 131 132 |
# File 'lib/sof/cycle.rb', line 130 def valid_periods @valid_periods end |
Instance Attribute Details
#parser ⇒ Object (readonly)
Returns the value of attribute parser.
216 217 218 |
# File 'lib/sof/cycle.rb', line 216 def parser @parser end |
Class Method Details
.class_for_kind(sym) ⇒ Object
Return the class handling the kind
97 98 99 100 101 |
# File 'lib/sof/cycle.rb', line 97 def class_for_kind(sym) Cycle.cycle_handlers.find do |klass| klass.handles?(sym) end || raise(InvalidKind, "':#{sym}' is not a valid kind of Cycle") end |
.class_for_notation_id(notation_id) ⇒ Object
Return the appropriate class for the give notation id
86 87 88 89 90 |
# File 'lib/sof/cycle.rb', line 86 def class_for_notation_id(notation_id) Cycle.cycle_handlers.find do |klass| klass.notation_id == notation_id end || raise(InvalidKind, "'#{notation_id}' is not a valid kind of #{name}") end |
.cycle_handlers ⇒ Object
150 151 152 |
# File 'lib/sof/cycle.rb', line 150 def cycle_handlers @cycle_handlers ||= Set.new end |
.dump(cycle_or_string) ⇒ Object
Turn a cycle or notation string into a hash
17 18 19 20 21 22 23 |
# File 'lib/sof/cycle.rb', line 17 def dump(cycle_or_string) if cycle_or_string.is_a? Cycle cycle_or_string else Cycle.for(cycle_or_string) end.to_h end |
.for(notation) ⇒ Cycle
Return a Cycle object from a notation string
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/sof/cycle.rb', line 64 def for(notation) return notation if notation.is_a? Cycle return notation if notation.is_a? Cycles::Dormant parser = Parser.new(notation) unless parser.valid? raise InvalidInput, "'#{notation}' is not a valid input" end cycle = Cycle.cycle_handlers.find do |klass| parser.parses?(klass.notation_id) end.new(notation, parser:) return cycle if parser.active? Cycles::Dormant.new(cycle, parser:) end |
.handles?(sym) ⇒ Boolean
146 147 148 |
# File 'lib/sof/cycle.rb', line 146 def handles?(sym) kind.to_s == sym.to_s end |
.inherited(klass) ⇒ Object
154 155 156 |
# File 'lib/sof/cycle.rb', line 154 def inherited(klass) Cycle.cycle_handlers << klass end |
.legend ⇒ Hash
Return a legend explaining all notation components
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/sof/cycle.rb', line 106 def legend { "quantity" => { "V" => { description: "Volume - the number of times something should occur", examples: ["V1L1D - once in the prior 1 day", "V3L3D - three times in the prior 3 days", "V10L10D - ten times in the prior 10 days"] } }, "kind" => build_kind_legend, "period" => build_period_legend, "date" => { "F" => { description: "From - specifies the anchor date for Within cycles", examples: ["F2024-01-01 - from January 1, 2024", "F2024-12-31 - from December 31, 2024"] } } } end |
.load(hash) ⇒ Object
Return a Cycle object from a hash
26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/sof/cycle.rb', line 26 def load(hash) symbolized_hash = hash.symbolize_keys cycle_class = class_for_kind(symbolized_hash[:kind]) unless cycle_class.valid_periods.empty? cycle_class.validate_period( TimeSpan.notation_id_from_name(symbolized_hash[:period]) ) end Cycle.for notation(symbolized_hash) rescue TimeSpan::InvalidPeriod => exc raise InvalidPeriod, exc. end |
.notation(hash) ⇒ String
Retun a notation string from a hash
45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/sof/cycle.rb', line 45 def notation(hash) volume_notation = "V#{hash.fetch(:volume) { 1 }}" return volume_notation if hash[:kind].nil? || hash[:kind].to_sym == :volume_only cycle_class = class_for_kind(hash[:kind].to_sym) [ volume_notation, cycle_class.notation_id, TimeSpan.notation(hash.slice(:period, :period_count)), hash.fetch(:from, nil) ].compact.join end |
.recurring? ⇒ Boolean
133 |
# File 'lib/sof/cycle.rb', line 133 def recurring? = raise "#{name} must implement #{__method__}" |
.validate_period(period) ⇒ Object
Raises an error if the given period isn’t in the list of valid periods.
139 140 141 142 143 144 |
# File 'lib/sof/cycle.rb', line 139 def validate_period(period) raise InvalidPeriod, " Invalid period value of '\#{period}' provided. Valid periods are:\n \#{valid_periods.join(\", \")}\n ERR\nend\n".squish unless valid_periods.include?(period) |
.volume_only? ⇒ Boolean
131 |
# File 'lib/sof/cycle.rb', line 131 def volume_only? = @volume_only |
Instance Method Details
#==(other) ⇒ Object
Cycles are considered equal if their hash representations are equal
245 |
# File 'lib/sof/cycle.rb', line 245 def ==(other) = to_h == other.to_h |
#as_json ⇒ Object
301 |
# File 'lib/sof/cycle.rb', line 301 def as_json(...) = notation |
#considered_dates(completion_dates, anchor: Date.current) ⇒ Object
260 261 262 |
# File 'lib/sof/cycle.rb', line 260 def considered_dates(completion_dates, anchor: Date.current) covered_dates(completion_dates, anchor:).max_by(volume) { it } end |
#cover?(date, anchor: Date.current) ⇒ Boolean
270 271 272 |
# File 'lib/sof/cycle.rb', line 270 def cover?(date, anchor: Date.current) range(anchor).cover?(date) end |
#covered_dates(dates, anchor: Date.current) ⇒ Object
264 265 266 267 268 |
# File 'lib/sof/cycle.rb', line 264 def covered_dates(dates, anchor: Date.current) dates.select do |date| cover?(date, anchor:) end end |
#expiration_of(_completion_dates, anchor: Date.current) ⇒ Object
281 |
# File 'lib/sof/cycle.rb', line 281 def expiration_of(_completion_dates, anchor: Date.current) = nil |
#extend_period(_ = nil) ⇒ Object
250 |
# File 'lib/sof/cycle.rb', line 250 def extend_period(_ = nil) = self |
#final_date(_anchor) ⇒ Object
Return the final date of the cycle
279 |
# File 'lib/sof/cycle.rb', line 279 def final_date(_anchor) = nil |
#from_data ⇒ Object
295 296 297 298 299 |
# File 'lib/sof/cycle.rb', line 295 def from_data return {} unless from {from: from} end |
#humanized_span ⇒ Object
276 |
# File 'lib/sof/cycle.rb', line 276 def humanized_span = [period_count, humanized_period].join(" ") |
#kind_inquiry ⇒ Object
225 |
# File 'lib/sof/cycle.rb', line 225 def kind_inquiry = ActiveSupport::StringInquirer.new(kind.to_s) |
#last_completed(dates) ⇒ Object
Return the most recent completion date from the supplied array of dates
248 |
# File 'lib/sof/cycle.rb', line 248 def last_completed(dates) = dates.compact.map(&:to_date).max |
#notation ⇒ Object
Return the cycle representation as a notation string
234 235 236 237 238 239 240 241 242 |
# File 'lib/sof/cycle.rb', line 234 def notation hash = to_h [ "V#{volume}", self.class.notation_id, time_span.notation, hash.fetch(:from, nil) ].compact.join end |
#range(anchor) ⇒ Object
274 |
# File 'lib/sof/cycle.rb', line 274 def range(anchor) = start_date(anchor)..final_date(anchor) |
#satisfied_by?(completion_dates, anchor: Date.current) ⇒ Boolean
From the supplied anchor date, are there enough in-window completions to satisfy the cycle?
256 257 258 |
# File 'lib/sof/cycle.rb', line 256 def satisfied_by?(completion_dates, anchor: Date.current) covered_dates(completion_dates, anchor:).size >= volume end |
#to_h ⇒ Object
285 286 287 288 289 290 291 292 293 |
# File 'lib/sof/cycle.rb', line 285 def to_h { kind:, volume:, period:, period_count:, **from_data } end |
#validate_period ⇒ Object
227 228 229 230 231 |
# File 'lib/sof/cycle.rb', line 227 def validate_period return if valid_periods.empty? self.class.validate_period(period_key) end |
#volume_to_delay_expiration(_completion_dates, anchor:) ⇒ Object
283 |
# File 'lib/sof/cycle.rb', line 283 def volume_to_delay_expiration(_completion_dates, anchor:) = 0 |