lib/checkoff/internal/task_selector_evaluator.rb in checkoff-0.44.0 vs lib/checkoff/internal/task_selector_evaluator.rb in checkoff-0.44.1

- old
+ new

@@ -1,381 +1,401 @@ # frozen_string_literal: true module Checkoff - # Base class to evaluate a task selector function given fully evaluated arguments - class FunctionEvaluator - # @param task_selector [Array<(Symbol, Array)>,String] - # @param tasks [Checkoff::Tasks] - def initialize(task_selector:, - tasks:) - @task_selector = task_selector - @tasks = tasks - end + module TaskSelectorClasses + # Base class to evaluate a task selector function given fully evaluated arguments + class FunctionEvaluator + # @param task_selector [Array<(Symbol, Array)>,String] + # @param tasks [Checkoff::Tasks] + def initialize(task_selector:, + tasks:) + @task_selector = task_selector + @tasks = tasks + end - # @sg-ignore - # @param _index [Integer] - def evaluate_arg?(_index) - true - end + # @sg-ignore + # @param _index [Integer] + def evaluate_arg?(_index) + true + end - # @sg-ignore - # @return [Boolean] - def matches? - raise 'Override me!' - end + # @sg-ignore + # @return [Boolean] + def matches? + raise 'Override me!' + end - private + private - # @param object [Object] - # @param fn_name [Symbol] - def fn?(object, fn_name) - object.is_a?(Array) && !object.empty? && [fn_name, fn_name.to_s].include?(object[0]) - end + # @param object [Object] + # @param fn_name [Symbol] + def fn?(object, fn_name) + object.is_a?(Array) && !object.empty? && [fn_name, fn_name.to_s].include?(object[0]) + end - # @sg-ignore - # @param task [Asana::Resources::Task] - # @param custom_field_gid [String] - # @return [Hash] - def pull_custom_field_or_raise(task, custom_field_gid) - # @type [Array<Hash>] - custom_fields = task.custom_fields - if custom_fields.nil? - raise "Could not find custom_fields under task (was 'custom_fields' included in 'extra_fields'?)" + # @sg-ignore + # @param task [Asana::Resources::Task] + # @param custom_field_gid [String] + # @return [Hash] + def pull_custom_field_or_raise(task, custom_field_gid) + # @type [Array<Hash>] + custom_fields = task.custom_fields + if custom_fields.nil? + raise "Could not find custom_fields under task (was 'custom_fields' included in 'extra_fields'?)" + end + + # @sg-ignore + # @type [Hash, nil] + matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid } + if matched_custom_field.nil? + raise "Could not find custom field with gid #{custom_field_gid} " \ + "in task #{task.gid} with custom fields #{custom_fields}" + end + + matched_custom_field end + # @return [Array<(Symbol, Array)>] + attr_reader :task_selector + # @sg-ignore - # @type [Hash, nil] - matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid } - if matched_custom_field.nil? - raise "Could not find custom field with gid #{custom_field_gid} " \ - "in task #{task.gid} with custom fields #{custom_fields}" + # @param custom_field [Hash] + # @return [Array<String>] + def pull_enum_values(custom_field) + resource_subtype = custom_field.fetch('resource_subtype') + case resource_subtype + when 'enum' + [custom_field.fetch('enum_value')] + when 'multi_enum' + custom_field.fetch('multi_enum_values') + else + raise "Teach me how to handle resource_subtype #{resource_subtype}" + end end - matched_custom_field - end + # @param custom_field [Hash] + # @param enum_value [Object, nil] + # @return [Array<String>] + def find_gids(custom_field, enum_value) + if enum_value.nil? + [] + else + raise "Unexpected enabled value on custom field: #{custom_field}" if enum_value.fetch('enabled') == false - # @return [Array<(Symbol, Array)>] - attr_reader :task_selector + [enum_value.fetch('gid')] + end + end - # @sg-ignore - # @param custom_field [Hash] - # @return [Array<String>] - def pull_enum_values(custom_field) - resource_subtype = custom_field.fetch('resource_subtype') - case resource_subtype - when 'enum' - [custom_field.fetch('enum_value')] - when 'multi_enum' - custom_field.fetch('multi_enum_values') - else - raise "Teach me how to handle resource_subtype #{resource_subtype}" + # @param task [Asana::Resources::Task] + # @param custom_field_gid [String] + # @return [Array<String>] + def pull_custom_field_values_gids(task, custom_field_gid) + custom_field = pull_custom_field_or_raise(task, custom_field_gid) + pull_enum_values(custom_field).flat_map do |enum_value| + find_gids(custom_field, enum_value) + end end - end - # @param custom_field [Hash] - # @param enum_value [Object, nil] - # @return [Array<String>] - def find_gids(custom_field, enum_value) - if enum_value.nil? - [] - else - raise "Unexpected enabled value on custom field: #{custom_field}" if enum_value.fetch('enabled') == false + # @sg-ignore + # @param task [Asana::Resources::Task] + # @param custom_field_name [String] + # @return [Hash, nil] + def pull_custom_field_by_name(task, custom_field_name) + custom_fields = task.custom_fields + if custom_fields.nil? + raise "custom fields not found on task - did you add 'custom_fields' in your extra_fields argument?" + end - [enum_value.fetch('gid')] + # @sg-ignore + # @type [Hash, nil] + custom_fields.find { |field| field.fetch('name') == custom_field_name } end - end - # @param task [Asana::Resources::Task] - # @param custom_field_gid [String] - # @return [Array<String>] - def pull_custom_field_values_gids(task, custom_field_gid) - custom_field = pull_custom_field_or_raise(task, custom_field_gid) - pull_enum_values(custom_field).flat_map do |enum_value| - find_gids(custom_field, enum_value) + # @param task [Asana::Resources::Task] + # @param custom_field_name [String] + # @return [Hash] + def pull_custom_field_by_name_or_raise(task, custom_field_name) + custom_field = pull_custom_field_by_name(task, custom_field_name) + if custom_field.nil? + raise "Could not find custom field with name #{custom_field_name} " \ + "in task #{task.gid} with custom fields #{task.custom_fields}" + end + custom_field end end - # @sg-ignore - # @param task [Asana::Resources::Task] - # @param custom_field_name [String] - # @return [Hash, nil] - def pull_custom_field_by_name(task, custom_field_name) - custom_fields = task.custom_fields - if custom_fields.nil? - raise "custom fields not found on task - did you add 'custom_fields' in your extra_fields argument?" + # :and function + class AndFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :and + + def matches? + fn?(task_selector, FUNCTION_NAME) end - # @sg-ignore - # @type [Hash, nil] - custom_fields.find { |field| field.fetch('name') == custom_field_name } + # @param _task [Asana::Resources::Task] + # @param lhs [Object] + # @param rhs [Object] + # @return [Object] + def evaluate(_task, lhs, rhs) + lhs && rhs + end end - # @param task [Asana::Resources::Task] - # @param custom_field_name [String] - # @return [Hash] - def pull_custom_field_by_name_or_raise(task, custom_field_name) - custom_field = pull_custom_field_by_name(task, custom_field_name) - if custom_field.nil? - raise "Could not find custom field with name #{custom_field_name} " \ - "in task #{task.gid} with custom fields #{task.custom_fields}" + # :not function + class NotFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :not + + def matches? + fn?(task_selector, FUNCTION_NAME) end - custom_field - end - end - # :and function - class AndFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :and) + # @param _task [Asana::Resources::Task] + # @param subvalue [Object] + # @return [Boolean] + def evaluate(_task, subvalue) + !subvalue + end end - # @param _task [Asana::Resources::Task] - # @param lhs [Object] - # @param rhs [Object] - # @return [Object] - def evaluate(_task, lhs, rhs) - lhs && rhs - end - end + # :nil? function + class NilPFunctionEvaluator < FunctionEvaluator + def matches? + fn?(task_selector, :nil?) + end - # :not function - class NotFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :not) + # @param _task [Asana::Resources::Task] + # @param subvalue [Object] + # @return [Boolean] + def evaluate(_task, subvalue) + subvalue.nil? + end end - # @param _task [Asana::Resources::Task] - # @param subvalue [Object] - # @return [Boolean] - def evaluate(_task, subvalue) - !subvalue - end - end + # :equals? function + class EqualsPFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :equals? - # :nil? function - class NilPFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :nil?) - end + def matches? + fn?(task_selector, FUNCTION_NAME) + end - # @param _task [Asana::Resources::Task] - # @param subvalue [Object] - # @return [Boolean] - def evaluate(_task, subvalue) - subvalue.nil? + # @param _task [Asana::Resources::Task] + # @param lhs [Object] + # @param rhs [Object] + # @return [Boolean] + def evaluate(_task, lhs, rhs) + lhs == rhs + end end - end - # :equals? function - class EqualsPFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :equals?) - end + # :tag function + class TagPFunctionEvaluator < FunctionEvaluator + def matches? + fn?(task_selector, :tag) + end - # @param _task [Asana::Resources::Task] - # @param lhs [Object] - # @param rhs [Object] - # @return [Boolean] - def evaluate(_task, lhs, rhs) - lhs == rhs - end - end + # @param _index [Integer] + def evaluate_arg?(_index) + false + end - # :tag function - class TagPFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :tag) + # @sg-ignore + # @param task [Asana::Resources::Task] + # @param tag_name [String] + # @return [Boolean] + def evaluate(task, tag_name) + task.tags.map(&:name).include? tag_name + end end - # @param _index [Integer] - def evaluate_arg?(_index) - false - end + # :due function + class DuePFunctionEvaluator < FunctionEvaluator + def matches? + fn?(task_selector, :due) + end - # @sg-ignore - # @param task [Asana::Resources::Task] - # @param tag_name [String] - # @return [Boolean] - def evaluate(task, tag_name) - task.tags.map(&:name).include? tag_name + # @param task [Asana::Resources::Task] + # @return [Boolean] + def evaluate(task) + @tasks.task_ready?(task) + end end - end - # :due function - class DuePFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :due) - end + # :due_date_set function + class DueDateSetPFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :due_date_set - # @param task [Asana::Resources::Task] - # @return [Boolean] - def evaluate(task) - @tasks.task_ready?(task) - end - end + def matches? + fn?(task_selector, FUNCTION_NAME) + end - # :due_date_set function - class DueDateSetPFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :due_date_set) + # @sg-ignore + # @param task [Asana::Resources::Task] + # @return [Boolean] + def evaluate(task) + !task.due_at.nil? || !task.due_on.nil? + end end - # @sg-ignore - # @param task [Asana::Resources::Task] - # @return [Boolean] - def evaluate(task) - !task.due_at.nil? || !task.due_on.nil? - end - end + # :custom_field_value function + class CustomFieldValueFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :custom_field_value - # :custom_field_value function - class CustomFieldValueFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :custom_field_value) - end + def matches? + fn?(task_selector, FUNCTION_NAME) + end - # @param _index [Integer] - def evaluate_arg?(_index) - false - end + # @param _index [Integer] + def evaluate_arg?(_index) + false + end - # @param task [Asana::Resources::Task] - # @param custom_field_name [String] - # @return [String, nil] - def evaluate(task, custom_field_name) - custom_field = pull_custom_field_by_name(task, custom_field_name) - return nil if custom_field.nil? + # @param task [Asana::Resources::Task] + # @param custom_field_name [String] + # @return [String, nil] + def evaluate(task, custom_field_name) + custom_field = pull_custom_field_by_name(task, custom_field_name) + return nil if custom_field.nil? - custom_field['display_value'] + custom_field['display_value'] + end end - end - # :custom_field_gid_value function - class CustomFieldGidValueFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :custom_field_gid_value) - end + # :custom_field_gid_value function + class CustomFieldGidValueFunctionEvaluator < FunctionEvaluator + def matches? + fn?(task_selector, :custom_field_gid_value) + end - def evaluate_arg?(_index) - false - end + def evaluate_arg?(_index) + false + end - # @sg-ignore - # @param task [Asana::Resources::Task] - # @param custom_field_gid [String] - # @return [String, nil] - def evaluate(task, custom_field_gid) - custom_field = pull_custom_field_or_raise(task, custom_field_gid) - custom_field['display_value'] + # @sg-ignore + # @param task [Asana::Resources::Task] + # @param custom_field_gid [String] + # @return [String, nil] + def evaluate(task, custom_field_gid) + custom_field = pull_custom_field_or_raise(task, custom_field_gid) + custom_field['display_value'] + end end - end - # :custom_field_gid_value_contains_any_gid function - class CustomFieldGidValueContainsAnyGidFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :custom_field_gid_value_contains_any_gid) - end + # :custom_field_gid_value_contains_any_gid function + class CustomFieldGidValueContainsAnyGidFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :custom_field_gid_value_contains_any_gid - def evaluate_arg?(_index) - false - end + def matches? + fn?(task_selector, FUNCTION_NAME) + end - # @param task [Asana::Resources::Task] - # @param custom_field_gid [String] - # @param custom_field_values_gids [Array<String>] - # @return [Boolean] - def evaluate(task, custom_field_gid, custom_field_values_gids) - actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid) + def evaluate_arg?(_index) + false + end - actual_custom_field_values_gids.any? do |custom_field_value| - custom_field_values_gids.include?(custom_field_value) + # @param task [Asana::Resources::Task] + # @param custom_field_gid [String] + # @param custom_field_values_gids [Array<String>] + # @return [Boolean] + def evaluate(task, custom_field_gid, custom_field_values_gids) + actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid) + + actual_custom_field_values_gids.any? do |custom_field_value| + custom_field_values_gids.include?(custom_field_value) + end end end - end - # :custom_field_gid_value_contains_all_gids function - class CustomFieldGidValueContainsAllGidsFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :custom_field_gid_value_contains_all_gids) - end + # :custom_field_gid_value_contains_all_gids function + class CustomFieldGidValueContainsAllGidsFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :custom_field_gid_value_contains_all_gids - def evaluate_arg?(_index) - false - end + def matches? + fn?(task_selector, FUNCTION_NAME) + end - # @param task [Asana::Resources::Task] - # @param custom_field_gid [String] - # @param custom_field_values_gids [Array<String>] - # @return [Boolean] - def evaluate(task, custom_field_gid, custom_field_values_gids) - actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid) + def evaluate_arg?(_index) + false + end - custom_field_values_gids.all? do |custom_field_value| - actual_custom_field_values_gids.include?(custom_field_value) + # @param task [Asana::Resources::Task] + # @param custom_field_gid [String] + # @param custom_field_values_gids [Array<String>] + # @return [Boolean] + def evaluate(task, custom_field_gid, custom_field_values_gids) + actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid) + + custom_field_values_gids.all? do |custom_field_value| + actual_custom_field_values_gids.include?(custom_field_value) + end end end - end - # :custom_field_less_than_n_days_from_now function - class CustomFieldLessThanNDaysFromNowFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :custom_field_less_than_n_days_from_now) - end + # :custom_field_less_than_n_days_from_now function + class CustomFieldLessThanNDaysFromNowFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :custom_field_less_than_n_days_from_now - def evaluate_arg?(_index) - false - end + def matches? + fn?(task_selector, FUNCTION_NAME) + end - # @param task [Asana::Resources::Task] - # @param custom_field_name [String] - # @param num_days [Integer] - # @return [Boolean] - def evaluate(task, custom_field_name, num_days) - custom_field = pull_custom_field_by_name_or_raise(task, custom_field_name) + def evaluate_arg?(_index) + false + end - time_str = custom_field.fetch('display_value') - time = Time.parse(time_str) - n_days_from_now = (Time.now + (num_days * 24 * 60 * 60)) - time < n_days_from_now - end - end + # @param task [Asana::Resources::Task] + # @param custom_field_name [String] + # @param num_days [Integer] + # @return [Boolean] + def evaluate(task, custom_field_name, num_days) + custom_field = pull_custom_field_by_name_or_raise(task, custom_field_name) - # :custom_field_greater_than_or_equal_to_n_days_from_now function - class CustomFieldGreaterThanOrEqualToNDaysFromNowFunctionEvaluator < FunctionEvaluator - def matches? - fn?(task_selector, :custom_field_greater_than_or_equal_to_n_days_from_now) + time_str = custom_field.fetch('display_value') + time = Time.parse(time_str) + n_days_from_now = (Time.now + (num_days * 24 * 60 * 60)) + time < n_days_from_now + end end - def evaluate_arg?(_index) - false - end + # :custom_field_greater_than_or_equal_to_n_days_from_now function + class CustomFieldGreaterThanOrEqualToNDaysFromNowFunctionEvaluator < FunctionEvaluator + FUNCTION_NAME = :custom_field_greater_than_or_equal_to_n_days_from_now - # @param task [Asana::Resources::Task] - # @param custom_field_name [String] - # @param num_days [Integer] - # @return [Boolean] - def evaluate(task, custom_field_name, num_days) - custom_field = pull_custom_field_by_name_or_raise(task, custom_field_name) + def matches? + fn?(task_selector, FUNCTION_NAME) + end - time_str = custom_field.fetch('display_value') - time = Time.parse(time_str) - n_days_from_now = (Time.now + (num_days * 24 * 60 * 60)) - time >= n_days_from_now - end - end + def evaluate_arg?(_index) + false + end - # String literals - class StringLiteralEvaluator < FunctionEvaluator - def matches? - task_selector.is_a?(String) + # @param task [Asana::Resources::Task] + # @param custom_field_name [String] + # @param num_days [Integer] + # @return [Boolean] + def evaluate(task, custom_field_name, num_days) + custom_field = pull_custom_field_by_name_or_raise(task, custom_field_name) + + time_str = custom_field.fetch('display_value') + time = Time.parse(time_str) + n_days_from_now = (Time.now + (num_days * 24 * 60 * 60)) + time >= n_days_from_now + end end - # @sg-ignore - # @param _task [Asana::Resources::Task] - # @return [String] - def evaluate(_task) - task_selector + # String literals + class StringLiteralEvaluator < FunctionEvaluator + def matches? + task_selector.is_a?(String) + end + + # @sg-ignore + # @param _task [Asana::Resources::Task] + # @return [String] + def evaluate(_task) + task_selector + end end end # Evaluator task selectors against a task class TaskSelectorEvaluator @@ -386,23 +406,23 @@ @task = task @tasks = tasks end FUNCTION_EVALUTORS = [ - NotFunctionEvaluator, - NilPFunctionEvaluator, - EqualsPFunctionEvaluator, - TagPFunctionEvaluator, - CustomFieldValueFunctionEvaluator, - CustomFieldGidValueFunctionEvaluator, - CustomFieldGidValueContainsAnyGidFunctionEvaluator, - CustomFieldGidValueContainsAllGidsFunctionEvaluator, - AndFunctionEvaluator, - DuePFunctionEvaluator, - DueDateSetPFunctionEvaluator, - CustomFieldLessThanNDaysFromNowFunctionEvaluator, - CustomFieldGreaterThanOrEqualToNDaysFromNowFunctionEvaluator, - StringLiteralEvaluator, + Checkoff::TaskSelectorClasses::NotFunctionEvaluator, + Checkoff::TaskSelectorClasses::NilPFunctionEvaluator, + Checkoff::TaskSelectorClasses::EqualsPFunctionEvaluator, + Checkoff::TaskSelectorClasses::TagPFunctionEvaluator, + Checkoff::TaskSelectorClasses::CustomFieldValueFunctionEvaluator, + Checkoff::TaskSelectorClasses::CustomFieldGidValueFunctionEvaluator, + Checkoff::TaskSelectorClasses::CustomFieldGidValueContainsAnyGidFunctionEvaluator, + Checkoff::TaskSelectorClasses::CustomFieldGidValueContainsAllGidsFunctionEvaluator, + Checkoff::TaskSelectorClasses::AndFunctionEvaluator, + Checkoff::TaskSelectorClasses::DuePFunctionEvaluator, + Checkoff::TaskSelectorClasses::DueDateSetPFunctionEvaluator, + Checkoff::TaskSelectorClasses::CustomFieldLessThanNDaysFromNowFunctionEvaluator, + Checkoff::TaskSelectorClasses::CustomFieldGreaterThanOrEqualToNDaysFromNowFunctionEvaluator, + Checkoff::TaskSelectorClasses::StringLiteralEvaluator, ].freeze # @param task_selector [Array] # @return [Boolean, Object, nil] def evaluate(task_selector)