botanicus/now-task-manager

View on GitHub
lib/pomodoro/formats/today/task_list.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

require 'pomodoro/formats/today'

module Pomodoro::Formats::Today
  class TaskList
    include Enumerable

    # @since 0.2
    attr_reader :time_frames

    # Create a list of time frames and define a shortcut method on the task list
    # for accessing them by {TimeFrame#method_name their #method_name}.
    #
    # @param [Array<TimeFrame>] time_frames
    # @see TimeFrame#method_name
    # @since 0.2
    #
    # @example
    #   require 'pomodoro/formats/today'
    #
    #   task_list = Pomodoro::Formats::Today::TaskList.new(
    #     Pomodoro::Formats::Today::TimeFrame.new(
    #       name: 'Morning routine', start_time: Hour.parse('7:50'), items: [
    #         Pomodoro::Formats::Today::Task.new(status: :done, body: 'Headspace.')
    #       ]
    #     )
    #   )
    #
    #   # Now you can access the time frame by their method name.
    #   task_list.morning_routine
    def initialize(*time_frames)
      @time_frames = time_frames

      time_frame_methods = [:method_name]
      unless time_frames.is_a?(Array) && time_frames.all? { |item| time_frame_methods.all? { |method| item.respond_to?(method) } }
        raise ArgumentError, "Time frames is supposed to be an array of TimeFrame-like instances, was #{@time_frames.inspect}."
      end

      time_frames.each do |time_frame|
        self.define_singleton_method(time_frame.method_name) do
          time_frame
        end
      end
    end

    def <<(time_frame)
      self.define_singleton_method(time_frame.method_name) do
        time_frame
      end

      @time_frames << time_frame
    end

    # Iterate over the time frames.
    #
    # @yieldparam [TimeFrame] time_frame
    # @since 0.2
    def each(&block)
      @time_frames.each(&block)
    end

    # Iterate over the tasks of each time frame.
    #
    # @yieldparam [Task] task
    # @since 0.2
    def each_task(&block)
      @time_frames.map(&:tasks).flatten.each(&block)
    end

    def each_task_with_time_frame(&block)
      if block
        @time_frames.each do |time_frame|
          time_frame.tasks.each do |task|
            block.call(time_frame, task)
          end
        end
      else
        self.enum_for(:each_task_with_time_frame)
      end
    end

    # Return overall duration.
    #
    # @return [Hour]
    # @since 0.2
    def duration
      self.with_prev_and_next.reduce(0) do |sum, (prev_time_frame, time_frame, next_time_frame)|
        prev_time_frame_end_time   = prev_time_frame ? prev_time_frame.end_time   : Hour.parse('0:00')
        next_time_frame_start_time = next_time_frame ? next_time_frame.start_time : Hour.parse('23:59')
        time_frame.duration(prev_time_frame_end_time, next_time_frame_start_time) + sum
      end
    end

    # @example
    #   task_list.with_prev_and_next.each do |prev_time_frame, time_frame, next_time_frame|
    #   end
    def with_prev_and_next(&block)
      # return enum_for(:with_prev_and_next) if block.nil?

      Enumerator.new do |yielder|
        if @time_frames.length == 1
          yielder << [nil, @time_frames[0], nil]
        elsif @time_frames.length > 1
          yielder << [nil, @time_frames[0], @time_frames[1]]
          @time_frames[1..-2].each.with_index do |time_frame, index|
            yielder << [@time_frames[index], time_frame, @time_frames[index + 2]]
          end
          yielder << [@time_frames[-2], @time_frames[-1], nil]
        end
      end
    end

    # @example
    #   task_list.with_prev_and_next_times.each do |time_frame, prev_time_frame_end_time, next_time_frame_start_time|
    #   end
    def with_prev_and_next_times
      # TODO
    end

    # Return a today task list formatted string.
    #
    # @since 0.2
    def to_s
      self.time_frames.reduce(nil) do |buffer, time_frame|
        buffer ? "#{buffer}\n#{time_frame}" : time_frame.to_s
      end
    end

    # @!group Finders

    # Return the currently active task, regardless of the time frame we are in.
    #
    # @return [Task, nil]
    # @since 0.2
    def active_task
      self.each_task.find(&:in_progress?)
    end

    # Return the currently active time frame.
    #
    # @return [TimeFrame, nil]
    # @since 0.2
    def current_time_frame(current_time = Hour.now)
      result = self.with_prev_and_next.find do |prev_time_frame, time_frame, next_time_frame|
        prev_time_frame_end_time = prev_time_frame&.end_time
        next_time_frame_start_time = next_time_frame&.start_time
        time_frame.active?(current_time, prev_time_frame_end_time, next_time_frame_start_time)
      end

      result && result[1]
    end

    # def has_unfinished_tasks?
    #   @time_frames.any?(&:has_unfinished_tasks?)
    # end

    # @!endgroup
  end
end