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