-
Notifications
You must be signed in to change notification settings - Fork 0
Restrict classes to the gem namespace #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require_relative "../cycle" | ||
| require "active_support/core_ext/hash/keys" | ||
| require "active_support/core_ext/object/blank" | ||
| require "active_support/core_ext/object/inclusion" | ||
| require "active_support/core_ext/hash/reverse_merge" | ||
| require "active_support/isolated_execution_state" | ||
|
|
||
| module SOF | ||
| class Cycle | ||
| # This class is not intended to be referenced directly. | ||
| # This is an internal implementation of Cycle behavior. | ||
| class Parser | ||
|
saturnflyer marked this conversation as resolved.
|
||
| extend Forwardable | ||
|
|
||
| PARTS_REGEX = / | ||
| ^(?<vol>V(?<volume>\d*))? # optional volume | ||
| (?<set>(?<kind>LE|L|C|W|E|I) # kind | ||
| (?<period_count>\d+) # period count | ||
| (?<period_key>D|W|M|Q|Y)?)? # period_key | ||
| (?<from>F(?<from_date>\d{4}-\d{2}-\d{2}))?$ # optional from | ||
| /ix | ||
|
|
||
| # Deliberately a method, not a constant: handlers register themselves | ||
| # through Cycle.inherited as each file in sof/cycles loads, which happens | ||
| # after this file. A constant would capture the empty set and leave every | ||
| # dormant-capable kind unrecognized. | ||
| def self.dormant_capable_kinds | ||
| Cycle.cycle_handlers.select(&:dormant_capable?).map(&:notation_id).compact.freeze | ||
| end | ||
|
|
||
| def self.for(notation_or_parser) | ||
| return notation_or_parser if notation_or_parser.is_a? self | ||
|
|
||
| new(notation_or_parser) | ||
| end | ||
|
|
||
| def self.load(hash) | ||
|
saturnflyer marked this conversation as resolved.
|
||
| hash.symbolize_keys! | ||
| hash.reverse_merge!(volume: 1) | ||
| keys = %i[volume kind period_count period_key] | ||
| str = "V#{hash.values_at(*keys).join}" | ||
| return new(str) unless hash[:from_date] | ||
|
|
||
| new([str, "F#{hash[:from_date]}"].join) | ||
| end | ||
|
|
||
| def initialize(notation) | ||
| @notation = notation&.upcase | ||
|
saturnflyer marked this conversation as resolved.
|
||
| @match = @notation&.match(PARTS_REGEX) | ||
| @time_span = nil | ||
| end | ||
|
|
||
| attr_reader :match, :notation | ||
|
|
||
| delegate [:dormant_capable_kinds] => "self.class" | ||
| delegate [:period, :humanized_period] => :time_span | ||
|
|
||
| # Return a TimeSpan object for the period and period_count | ||
| def time_span | ||
|
saturnflyer marked this conversation as resolved.
|
||
| @time_span ||= TimeSpan.for(period_count, period_key) | ||
| end | ||
|
|
||
| def valid? = match.present? | ||
|
|
||
| def inspect = notation | ||
|
saturnflyer marked this conversation as resolved.
|
||
| alias_method :to_s, :inspect | ||
|
|
||
| def activated_notation(date) | ||
| return notation unless dormant_capable? | ||
|
|
||
| self.class.load(to_h.merge(from_date: date.to_date)).notation | ||
|
saturnflyer marked this conversation as resolved.
|
||
| end | ||
|
|
||
| def ==(other) = other.to_h == to_h | ||
|
|
||
| def to_h | ||
|
saturnflyer marked this conversation as resolved.
|
||
| { | ||
| volume:, | ||
| kind:, | ||
| period_count:, | ||
| period_key:, | ||
| from_date: | ||
| } | ||
| end | ||
|
|
||
| def parses?(notation_id) = kind == notation_id | ||
|
|
||
| def active? = !dormant? | ||
|
|
||
| def dormant? = dormant_capable? && from_date.nil? | ||
|
|
||
| def dormant_capable? = kind.in?(dormant_capable_kinds) | ||
|
|
||
| def period_count = match[:period_count] | ||
|
|
||
| def period_key = match[:period_key] | ||
|
|
||
| def vol = match[:vol] || "V1" | ||
|
|
||
| def volume = (match[:volume] || 1).to_i | ||
|
|
||
| def from_data | ||
| return {} unless from | ||
|
|
||
| {from: from} | ||
| end | ||
|
|
||
| def from_date = match[:from_date] | ||
|
|
||
| def from = match[:from] | ||
|
|
||
| def kind = match[:kind] | ||
| end | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,238 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module SOF | ||
| class Cycle | ||
| # This class is not intended to be referenced directly. | ||
| # This is an internal implementation of Cycle behavior. | ||
| class TimeSpan | ||
| extend Forwardable | ||
|
|
||
| # TimeSpan objects map Cycle notations to behaviors for their periods | ||
| # | ||
| # For example: | ||
| # 'M' => TimeSpan::DatePeriod::Month | ||
| # 'Y' => TimeSpan::DatePeriod::Year | ||
| # Read each DatePeriod subclass for more information. | ||
| # | ||
| class InvalidPeriod < StandardError; end | ||
|
|
||
| class << self | ||
| # Return a time_span for the given count and period | ||
| def for(count, period) | ||
| case count.to_i | ||
| when 0 | ||
| TimeSpanNothing | ||
| when 1 | ||
| TimeSpanOne | ||
| else | ||
| self | ||
| end.new(count, period) | ||
| end | ||
|
|
||
| # Return a notation string from a hash | ||
| def notation(hash) | ||
| return unless hash.key?(:period) && hash[:period].present? | ||
|
|
||
| [ | ||
| hash.fetch(:period_count) { 1 }, | ||
| notation_id_from_name(hash[:period]) | ||
| ].compact.join | ||
| end | ||
|
|
||
| # Return the notation character for the given period name | ||
| def notation_id_from_name(name) | ||
| type = DatePeriod.types.find do |klass| | ||
| klass.period.to_s == name.to_s | ||
| end | ||
|
|
||
| raise InvalidPeriod, "'#{name}' is not a valid period" unless type | ||
|
|
||
| type.code | ||
| end | ||
| end | ||
|
|
||
| def notation | ||
| [period_count, code].join | ||
| end | ||
|
|
||
| # Class used to calculate the windows of time so that | ||
| # a TimeSpan object will know the correct end of year, | ||
| # quarter, etc. | ||
| class DatePeriod | ||
| extend Forwardable | ||
|
|
||
| class << self | ||
| def for(count, period_notation) | ||
| @cached_periods ||= {} | ||
| @cached_periods[period_notation] ||= {} | ||
| @cached_periods[period_notation][count] ||= (for_notation(period_notation) || self).new(count) | ||
| @cached_periods[period_notation][count] | ||
| end | ||
|
|
||
| def for_notation(notation) | ||
| DatePeriod.types.find do |klass| | ||
| klass.code == notation.to_s.upcase | ||
| end | ||
| end | ||
|
|
||
| def types = @types ||= Set.new | ||
|
|
||
| def inherited(klass) | ||
| DatePeriod.types << klass | ||
| end | ||
|
|
||
| @period = nil | ||
| @code = nil | ||
| @interval = nil | ||
| attr_reader :period, :code, :interval | ||
| end | ||
|
|
||
| delegate [:period, :code, :interval] => "self.class" | ||
|
|
||
| def initialize(count) | ||
| @count = count | ||
| @end_date = {} | ||
| @begin_date = {} | ||
| end | ||
| attr_reader :count | ||
|
|
||
| def end_date(date) | ||
| @end_date[date] ||= date + duration | ||
| end | ||
|
|
||
| def begin_date(date) | ||
| @begin_date[date] ||= date - duration | ||
| end | ||
|
|
||
| def duration = count.send(period) | ||
|
|
||
| def end_of_period(_) = nil | ||
|
|
||
| def humanized_period | ||
| return period if count == 1 | ||
|
|
||
| "#{period}s" | ||
| end | ||
|
|
||
| class Year < self | ||
| @period = :year | ||
| @code = "Y" | ||
| @interval = "years" | ||
|
|
||
| def end_of_period(date) | ||
| date.end_of_year | ||
| end | ||
|
|
||
| def beginning_of_period(date) | ||
| date.beginning_of_year | ||
| end | ||
| end | ||
|
|
||
| class Quarter < self | ||
| @period = :quarter | ||
| @code = "Q" | ||
| @interval = "quarters" | ||
|
|
||
| def duration | ||
| (count * 3).months | ||
| end | ||
|
|
||
| def end_of_period(date) | ||
| date.end_of_quarter | ||
| end | ||
|
|
||
| def beginning_of_period(date) | ||
| date.beginning_of_quarter | ||
| end | ||
| end | ||
|
|
||
| class Month < self | ||
| @period = :month | ||
| @code = "M" | ||
| @interval = "months" | ||
|
|
||
| def end_of_period(date) | ||
| date.end_of_month | ||
| end | ||
|
|
||
| def beginning_of_period(date) | ||
| date.beginning_of_month | ||
| end | ||
| end | ||
|
|
||
| class Week < self | ||
| @period = :week | ||
| @code = "W" | ||
| @interval = "weeks" | ||
|
|
||
| def end_of_period(date) | ||
| date.end_of_week | ||
| end | ||
|
|
||
| def beginning_of_period(date) | ||
| date.beginning_of_week | ||
| end | ||
| end | ||
|
|
||
| class Day < self | ||
| @period = :day | ||
| @code = "D" | ||
| @interval = "days" | ||
|
|
||
| def end_of_period(date) | ||
| date | ||
| end | ||
|
|
||
| def beginning_of_period(date) | ||
| date | ||
| end | ||
| end | ||
| end | ||
| private_constant :DatePeriod | ||
|
|
||
| def initialize(count, period_id) | ||
| @count = Integer(count, exception: false) | ||
| @window = DatePeriod.for(period_count, period_id) | ||
| end | ||
| attr_reader :window | ||
|
|
||
| delegate [:end_date, :begin_date, :code] => :window | ||
|
|
||
| def end_date_of_period(date) | ||
| window.end_of_period(date) | ||
| end | ||
|
|
||
| def begin_date_of_period(date) | ||
| window.beginning_of_period(date) | ||
| end | ||
|
|
||
| # Integer value for the period count or nil | ||
| def period_count | ||
| @count | ||
| end | ||
|
|
||
| delegate [:period, :duration, :interval, :humanized_period] => :window | ||
|
|
||
| # Return a date according to the rules of the time_span | ||
| def final_date(date) | ||
| return unless period | ||
|
|
||
| window.end_date(date.to_date) | ||
| end | ||
|
|
||
| def to_h | ||
| { | ||
| period:, | ||
| period_count: | ||
| } | ||
| end | ||
|
|
||
| class TimeSpanNothing < self | ||
| end | ||
|
|
||
| class TimeSpanOne < self | ||
| def interval = humanized_period | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.