lib/checkoff/internal/task_selector_evaluator.rb in checkoff-0.40.0 vs lib/checkoff/internal/task_selector_evaluator.rb in checkoff-0.41.0

- old
+ new

@@ -1,43 +1,66 @@ # 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)>] + # @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 + # @return [Boolean] + def matches? + raise 'Override me!' + end + 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 + # @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 + # @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')] @@ -46,34 +69,71 @@ else raise "Teach me how to handle resource_subtype #{resource_subtype}" 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 [enum_value.fetch('gid')] 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) 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_field' in your extra_fields argument?" + end + + # @sg-ignore + # @type [Hash, nil] + custom_fields.find { |field| field.fetch('name') == custom_field_name } + 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}" + end + custom_field + end end # :and function class AndFunctionEvaluator < FunctionEvaluator def matches? fn?(task_selector, :and) end + # @param _task [Asana::Resources::Task] + # @param lhs [Object] + # @param rhs [Object] + # @return [Object] def evaluate(_task, lhs, rhs) lhs && rhs end end @@ -81,10 +141,13 @@ class NotFunctionEvaluator < FunctionEvaluator def matches? fn?(task_selector, :not) end + # @param _task [Asana::Resources::Task] + # @param subvalue [Object] + # @return [Boolean] def evaluate(_task, subvalue) !subvalue end end @@ -92,10 +155,13 @@ class NilPFunctionEvaluator < FunctionEvaluator def matches? fn?(task_selector, :nil?) end + # @param _task [Asana::Resources::Task] + # @param subvalue [Object] + # @return [Boolean] def evaluate(_task, subvalue) subvalue.nil? end end @@ -103,14 +169,19 @@ class TagPFunctionEvaluator < FunctionEvaluator def matches? fn?(task_selector, :tag) end + # @param _index [Integer] def evaluate_arg?(_index) false 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 end end @@ -118,10 +189,12 @@ class DuePFunctionEvaluator < FunctionEvaluator def matches? fn?(task_selector, :due) end + # @param task [Asana::Resources::Task] + # @return [Boolean] def evaluate(task) @tasks.task_ready?(task) end end @@ -129,10 +202,13 @@ class DueDateSetPFunctionEvaluator < FunctionEvaluator def matches? fn?(task_selector, :due_date_set) end + # @sg-ignore + # @param task [Asana::Resources::Task] + # @return [Boolean] def evaluate(task) !task.due_at.nil? || !task.due_on.nil? end end @@ -140,21 +216,20 @@ class CustomFieldValueFunctionEvaluator < FunctionEvaluator def matches? fn?(task_selector, :custom_field_value) 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_fields = task.custom_fields - if custom_fields.nil? - raise "custom fields not found on task - did you add 'custom_field' in your extra_fields argument?" - end - - custom_field = custom_fields.find { |field| field.fetch('name') == custom_field_name } + custom_field = pull_custom_field_by_name(task, custom_field_name) return nil if custom_field.nil? custom_field['display_value'] end end @@ -167,10 +242,14 @@ 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'] end end @@ -183,10 +262,14 @@ def evaluate_arg?(_index) false 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) actual_custom_field_values_gids.any? do |custom_field_value| custom_field_values_gids.include?(custom_field_value) @@ -202,21 +285,51 @@ def evaluate_arg?(_index) false 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) custom_field_values_gids.all? do |custom_field_value| actual_custom_field_values_gids.include?(custom_field_value) 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 + + def evaluate_arg?(_index) + false + 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) + + 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 + # Evaluator task selectors against a task class TaskSelectorEvaluator + # @param task [Asana::Resources::Task] + # @param tasks [Checkoff::Tasks] def initialize(task:, tasks: Checkoff::Tasks.new) @task = task @tasks = tasks end @@ -230,29 +343,39 @@ CustomFieldGidValueContainsAnyGidFunctionEvaluator, CustomFieldGidValueContainsAllGidsFunctionEvaluator, AndFunctionEvaluator, DuePFunctionEvaluator, DueDateSetPFunctionEvaluator, + CustomFieldLessThanNDaysFromNowFunctionEvaluator, ].freeze + # @param task_selector [Array] + # @return [Boolean, Object, nil] def evaluate(task_selector) - return true if task_selector == [] + return true if task_selector.empty? + # @param evaluator_class [Class<FunctionEvaluator>] FUNCTION_EVALUTORS.each do |evaluator_class| + # @sg-ignore + # @type [FunctionEvaluator] evaluator = evaluator_class.new(task_selector: task_selector, tasks: tasks) next unless evaluator.matches? return try_this_evaluator(task_selector, evaluator) end - raise "Syntax issue trying to handle #{task_selector}" + raise "Syntax issue trying to handle #{task_selector.inspect}" end private + # @sg-ignore + # @param task_selector [Array] + # @param evaluator [FunctionEvaluator] + # @return [Boolean, Object, nil] def try_this_evaluator(task_selector, evaluator) evaluated_args = task_selector[1..].map.with_index do |item, index| if evaluator.evaluate_arg?(index) evaluate(item) else @@ -261,8 +384,13 @@ end evaluator.evaluate(task, *evaluated_args) end - attr_reader :task, :tasks, :task_selector + # @return [Asana::Resources::Task] + attr_reader :task + # @return [Checkoff::Tasks] + attr_reader :tasks + # @return [Array<(Symbol, Array)>] + attr_reader :task_selector end end