class Nickel::ConstructInterpreter
Attributes
constructs[R]
curdate[R]
occurrences[R]
Public Class Methods
new(constructs, curdate)
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 8 def initialize(constructs, curdate) @constructs = constructs @curdate = curdate @occurrences = [] # output initialize_index_to_type_map initialize_user_input_style initialize_arrays_of_construct_indices initialize_sorted_time_map finalize_constructs end
Public Instance Methods
run()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 19 def run if found_dates occurrences_from_dates elsif found_one_date_span occurrences_from_one_date_span elsif found_recurrences_and_optional_date_span occurrences_from_recurrences_and_optional_date_span elsif found_wrappers_only occurrences_from_wrappers_only end end
Private Instance Methods
create_occurrence_for_each_time_in_time_map(occ_base, dindex) { |occ| ... }
click to toggle source
The @sorted_time_map hash has keys of date/datespans/recurrence indices (in this case date), and an array of time and time span indices as values. This checks to make sure that array of times is not empty, and if it is there are no times associated with this date construct. Huh? That does not explain this method… at all.
# File lib/nickel/construct_interpreter.rb, line 222 def create_occurrence_for_each_time_in_time_map(occ_base, dindex, &block) if !@sorted_time_map[dindex].empty? @sorted_time_map[dindex].each do |tindex| # tindex may be time index or time span index occ = occ_base.dup occ.start_time = start_time_from_tindex(tindex) if index_references_time(tindex) occ.start_time = @constructs[tindex].time elsif index_references_timespan(tindex) occ.start_time = @constructs[tindex].start_time occ.end_time = @constructs[tindex].end_time end yield(occ) end else yield(occ_base) end end
finalize_constructs()
click to toggle source
One of this methods functions will be to assign proper am/pm values to time and timespan constructs if they were not specified.
# File lib/nickel/construct_interpreter.rb, line 194 def finalize_constructs # First assign am/pm values to timespan constructs independent of # other times in timemap. finalize_timespan_constructs # Next we need to burn through the time map, find any start times # that are not firm, and set them based on previous firm times. # Note that @sorted_time_map has the format {date_index => [array, of, time, indices]} @sorted_time_map.each_value do |time_indices| # The time_indices array holds TimeConstruct and TimeSpanConstruct indices. # The time_array will hold an array of ZTime objects to modify (potentially) time_array = [] time_indices.each { |tindex| time_array << start_time_from_tindex(tindex) } ZTime.am_pm_modifier(*time_array) end # Finally, we need to modify the timespans based on the the time info from am_pm_modifier. # We also need to guess at any timespans that didn't get any help from am_pm_modifier. # i.e. we originally guessed at timespans independently of other time info in time map; # now that we have modified start times based on other info in time map, we can refine the # end times in our time spans. If we didn't pick them up before. finalize_timespan_constructs(true) end
finalize_timespan_constructs(guess = false)
click to toggle source
If guess is false, either start time or end time (but not both) must already be firm. The time that is not firm will be modified according to the firm time and set to firm.
# File lib/nickel/construct_interpreter.rb, line 177 def finalize_timespan_constructs(guess = false) @tsci.each do |i| st, et = @constructs[i].start_time, @constructs[i].end_time if st.firm && et.firm next # nothing to do if start and end times are both firm elsif !st.firm && et.firm st.modify_such_that_is_before(et) elsif st.firm && !et.firm et.modify_such_that_is_after(st) else et.guess_modify_such_that_is_after(st) if guess end end end
found_dates()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 240 def found_dates # One or more date constructs, NO date spans, NO recurrence, # possible wrappers, possible time constructs, possible time spans @dci.size > 0 && @dsci.size == 0 && @rci.size == 0 end
found_one_date_span()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 253 def found_one_date_span @dci.size == 0 && @dsci.size == 1 && @rci.size == 0 end
found_recurrences_and_optional_date_span()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 265 def found_recurrences_and_optional_date_span @dsci.size <= 1 && @rci.size >= 1 # dates are optional end
found_wrappers_only()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 290 def found_wrappers_only # This should really be "found length wrappers only", because @dci.size must be zero, # and start/end wrappers require a date. @dsci.size == 0 && @rci.size == 0 && @wci.size > 0 && @dci.size == 0 end
handle_datetime_time_map_inheritance(inherit_on, date_index)
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 133 def handle_datetime_time_map_inheritance(inherit_on, date_index) if @sorted_time_map[date_index].empty? # There are no times for this date, mark to be inherited inherit_on << date_index else # There are times for this date, use them for any indices marked as inherit_on. # Then clear the inherit_on array. inherit_on.each { |k| @sorted_time_map[k] = @sorted_time_map[date_index] } inherit_on = [] end inherit_on end
handle_timedate_time_map_inheritance(inherit_from, date_index)
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 146 def handle_timedate_time_map_inheritance(inherit_from, date_index) if @sorted_time_map[date_index].empty? # There are no times for this date, try inheriting from last batch of times. @sorted_time_map[date_index] = @sorted_time_map[inherit_from] if inherit_from else inherit_from = date_index end inherit_from end
index_references_time(index)
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 156 def index_references_time(index) @index_to_type_map[index] == :time end
index_references_timespan(index)
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 160 def index_references_timespan(index) @index_to_type_map[index] == :timespan end
initialize_arrays_of_construct_indices()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 65 def initialize_arrays_of_construct_indices @dci, @tci, @dsci, @tsci, @rci, @wci = [], [], [], [], [], [] @index_to_type_map.each do |i, type| case type when :date then @dci << i when :time then @tci << i when :datespan then @dsci << i when :timespan then @tsci << i when :recurrence then @rci << i when :wrapper then @wci << i end end end
initialize_index_to_type_map()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 33 def initialize_index_to_type_map # The @index_to_type_map hash looks like this: {0 => :date, 1 => :timespan, ...} # Each key represents the index in @constructs and the value represents that constructs class. @index_to_type_map = {} @constructs.each_with_index do |c, i| @index_to_type_map[i] = case c.class.name when 'Nickel::DateConstruct' then :date when 'Nickel::TimeConstruct' then :time when 'Nickel::DateSpanConstruct' then :datespan when 'Nickel::TimeSpanConstruct' then :timespan when 'Nickel::RecurrenceConstruct' then :recurrence when 'Nickel::WrapperConstruct' then :wrapper end end end
initialize_sorted_time_map()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 79 def initialize_sorted_time_map # Sorted time map has date/datespan/recurrence construct indices as keys, and # an array of time/timespan indices as values. @sorted_time_map = {} # Get all indices of date/datespan/recurrence constructs in the order they occurred. date_indices = (@dci + @dsci + @rci).sort # What is inhert_on about? If a user enters something like "wed and fri at 4pm" they # really want 4pm to be associated with wed and fri. For all dates that we don't find # associated times, we append the dates index to the inherit_on array. Then, once we # find a date associated with times, we copy the sorted_time_map at that index to the # other indices in the inherit_on array. # # If @user_input_style is :datetime, then inherit_on will hold date indices that must inherit # from the next date with associated times. If @user_input_style is :timedate, then # inherit_from will hold the last date index with associated times, and subsequent dates that # do not have associated times will inherit from this index. @user_input_style == :datetime ? inherit_on = [] : inherit_from = nil # Iterate date_indices and populate @sorted_time_map date_indices.each do |i| # Do not change i. j = i # Now find all time and time span construct indices between this index and a boundary. # Boundaries are any other index in date_indices, -1 (passed the first construct), # and @constructs.size (passed the last construct). map_to_indices = [] while (j = move_time_map_index(j)) && j != -1 && j != @constructs.size && !date_indices.include?(j) # boundaries (index_references_time(j) || index_references_timespan(j)) && map_to_indices << j end # NOTE: time/timespan indices are sorted by the order which they appeared, e.g. # their construct index number. @sorted_time_map[i] = map_to_indices.sort if @user_input_style == :datetime inherit_on = handle_datetime_time_map_inheritance(inherit_on, i) else inherit_from = handle_timedate_time_map_inheritance(inherit_from, i) end end end
initialize_user_input_style()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 49 def initialize_user_input_style # Initializes @user_input_style. # Determine user input style, i.e. "DATE TIME DATE TIME" OR "TIME DATE TIME DATE". # Determine user input style, i.e. "DATE TIME DATE TIME" OR "TIME DATE TIME DATE". case (@index_to_type_map[0] == :wrapper ? @index_to_type_map[1] : @index_to_type_map[0]) when :date then @user_input_style = :datetime when :time then @user_input_style = :timedate when :datespan then @user_input_style = :datetime when :timespan then @user_input_style = :timedate when :recurrence then @user_input_style = :datetime else # We only have wrappers. It doesn't matter which user style we choose. @user_input_style = :datetime end end
move_time_map_index(index)
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 123 def move_time_map_index(index) # The time map index must be moved based on user input style. For instance, # if a user enters dates then times, we must attach times to preceding dates, # meaning move forward. if @user_input_style == :datetime then index + 1 elsif @user_input_style == :timedate then index - 1 else fail 'ConstructInterpreter#move_time_map_index says: @user_input_style is not valid' end end
occ_base_opts_from_wrappers()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 301 def occ_base_opts_from_wrappers base_opts = {} # Must do type 0 and 1 wrappers first, imagine something like # "every friday starting next friday for 6 months". @wci.each do |wi| # Make sure the construct after the wrapper is a date. if @constructs[wi].wrapper_type == 0 && @dci.include?(wi + 1) base_opts[:start_date] = @constructs[wi + 1].date elsif @constructs[wi].wrapper_type == 1 && @dci.include?(wi + 1) base_opts[:end_date] = @constructs[wi + 1].date end end # Now pick up wrapper types 2,3,4 @wci.each do |wi| if @constructs[wi].wrapper_type >= 2 if base_opts[:start_date].nil? && base_opts[:end_date].nil? # span must start today base_opts[:start_date] = @curdate.dup base_opts[:end_date] = case @constructs[wi].wrapper_type when 2 then @curdate.add_days(@constructs[wi].wrapper_length) when 3 then @curdate.add_weeks(@constructs[wi].wrapper_length) when 4 then @curdate.add_months(@constructs[wi].wrapper_length) end elsif base_opts[:start_date] && base_opts[:end_date].nil? base_opts[:end_date] = case @constructs[wi].wrapper_type when 2 then base_opts[:start_date].add_days(@constructs[wi].wrapper_length) when 3 then base_opts[:start_date].add_weeks(@constructs[wi].wrapper_length) when 4 then base_opts[:start_date].add_months(@constructs[wi].wrapper_length) end elsif base_opts[:start_date].nil? && base_opts[:end_date] # for 6 months until jan 3rd base_opts[:start_date] = case @constructs[wi].wrapper_type when 2 then base_opts[:end_date].sub_days(@constructs[wi].wrapper_length) when 3 then base_opts[:end_date].sub_weeks(@constructs[wi].wrapper_length) when 4 then base_opts[:end_date].sub_months(@constructs[wi].wrapper_length) end end end end base_opts end
occurrences_from_dates()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 246 def occurrences_from_dates @dci.each do |dindex| occ_base = Occurrence.new(type: :single, start_date: @constructs[dindex].date) create_occurrence_for_each_time_in_time_map(occ_base, dindex) { |occ| @occurrences << occ } end end
occurrences_from_one_date_span()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 257 def occurrences_from_one_date_span occ_base = Occurrence.new(type: :daily, start_date: @constructs[@dsci[0]].start_date, end_date: @constructs[@dsci[0]].end_date, interval: 1) create_occurrence_for_each_time_in_time_map(occ_base, @dsci[0]) { |occ| @occurrences << occ } end
occurrences_from_recurrences_and_optional_date_span()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 269 def occurrences_from_recurrences_and_optional_date_span if @dsci.size == 1 # If a date span exists, it functions as wrapper. occ_base_opts = { start_date: @constructs[@dsci[0]].start_date, end_date: @constructs[@dsci[0]].end_date } else # Perhaps there are type 0 or type 1 wrappers to provide start/end dates. occ_base_opts = occ_base_opts_from_wrappers end @rci.each do |rcindex| # Construct#interpret returns an array of hashes, each hash represents a single occurrence. @constructs[rcindex].interpret.each do |rec_occ_base_opts| # RecurrenceConstruct#interpret returns base_opts for each occurrence, # but they must be merged with start/end dates, if supplied. occ_base = Occurrence.new(rec_occ_base_opts.merge(occ_base_opts)) # Attach times: create_occurrence_for_each_time_in_time_map(occ_base, rcindex) { |occ| @occurrences << occ } end end end
occurrences_from_wrappers_only()
click to toggle source
# File lib/nickel/construct_interpreter.rb, line 296 def occurrences_from_wrappers_only occ_base = { type: :daily, interval: 1 } @occurrences << Occurrence.new(occ_base.merge(occ_base_opts_from_wrappers)) end
start_time_from_tindex(tindex)
click to toggle source
Returns either @time or @start_time, depending on whether tindex references a Time or TimeSpan construct
# File lib/nickel/construct_interpreter.rb, line 165 def start_time_from_tindex(tindex) if index_references_time(tindex) return @constructs[tindex].time elsif index_references_timespan(tindex) return @constructs[tindex].start_time else fail 'ConstructInterpreter#start_time_from_tindex says: tindex does not reference a time or time span' end end