checkoff 0.58.1 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2b8e1f32a572bfcd4797199e3e8bf40a8bacfe3f20eb117a13f52e5f69feab5
4
- data.tar.gz: fe61f004dcc81088a23acee2f2fe03c8cec9fc107d1850cc6450d6bde99bdbdc
3
+ metadata.gz: 19a77e1d0a58d1d80b234b2dca2654bd2b37f289bd5ad7ee3ad6973459032e5a
4
+ data.tar.gz: '09ab639d4ddb008e09069ba819004b631bc6b0fc735b5332235d94d2b3671e7e'
5
5
  SHA512:
6
- metadata.gz: 2e77e9c93328415564d60bc1b3b1ca29bb678ce425477137392acd26d92acd81823a9fa9e6db189ce2a22045ca6dc45df32a8548a9265889e95f86196d238b1e
7
- data.tar.gz: 516063b25c108b17ac6039c3890cf375a623078b913a33fecb019ae4c9a4f76e2a1f3995fae4cb3c529d5dddcb2d9076f5ec07e940a464ed13330c5b63ab3fbd
6
+ metadata.gz: 3f49402138523699db239d85718dfbce19c588bc283354b7fd2efe48045512dd314fedbfa3e92d4e7b8874bb29895a6ae10348b094db7e156744fbd9162f22f5
7
+ data.tar.gz: 9d4c80f6a663a0b26053f904e8860b545775cbb13386d64e229e60d84abb765b1887c2811273df27801ab05503baff8402a38660af6daa45dec9280da9228321
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- checkoff (0.58.1)
15
+ checkoff (0.59.0)
16
16
  activesupport
17
17
  asana (> 0.10.0)
18
18
  cache_method
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'selector_classes/common'
4
+ require_relative 'selector_classes/project'
5
+ require_relative 'selector_evaluator'
6
+
7
+ module Checkoff
8
+ # Evaluates project selectors against a project
9
+ class ProjectSelectorEvaluator < SelectorEvaluator
10
+ # @param project [Asana::Resources::Project]
11
+ # @param projects [Checkoff::Projects]
12
+ def initialize(project:,
13
+ projects: Checkoff::Projects.new)
14
+ @item = project
15
+ @projects = projects
16
+ super()
17
+ end
18
+
19
+ private
20
+
21
+ FUNCTION_EVALUTORS = [
22
+ Checkoff::SelectorClasses::Common::CustomFieldValueContainsAnyValueFunctionEvaluator,
23
+ ].freeze
24
+
25
+ # @return [Array<Class<ProjectSelectorClasses::FunctionEvaluator>>]
26
+ def function_evaluators
27
+ FUNCTION_EVALUTORS
28
+ end
29
+
30
+ # @return [Hash]
31
+ def initializer_kwargs
32
+ { projects: projects }
33
+ end
34
+
35
+ # @return [Asana::Resources::Project]
36
+ attr_reader :item
37
+ # @return [Checkoff::Projects]
38
+ attr_reader :projects
39
+ end
40
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../function_evaluator'
4
+
5
+ module Checkoff
6
+ module SelectorClasses
7
+ module Common
8
+ # Base class to evaluate a project selector function given fully evaluated arguments
9
+ class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
10
+ # @param selector [Array<(Symbol, Array)>,String]
11
+ def initialize(selector:, **_kwargs)
12
+ @selector = selector
13
+ super()
14
+ end
15
+
16
+ private
17
+
18
+ # @param project [Asana::Resources::Project]
19
+ # @param field_name [Symbol]
20
+ #
21
+ # @sg-ignore
22
+ # @return [Date, nil]
23
+ def pull_date_field_by_name(project, field_name)
24
+ if field_name == :modified
25
+ return Time.parse(project.modified_at).to_date unless project.modified_at.nil?
26
+
27
+ return nil
28
+ end
29
+
30
+ if field_name == :due
31
+ return Time.parse(project.due_at).to_date unless project.due_at.nil?
32
+
33
+ return Date.parse(project.due_on) unless project.due_on.nil?
34
+
35
+ return nil
36
+ end
37
+
38
+ raise "Teach me how to handle field #{field_name}"
39
+ end
40
+
41
+ # @param project [Asana::Resources::Project]
42
+ # @param field_name [Symbol]
43
+ #
44
+ # @sg-ignore
45
+ # @return [Date, Time, nil]
46
+ def pull_date_or_time_field_by_name(project, field_name)
47
+ if field_name == :due
48
+ return Time.parse(project.due_at) unless project.due_at.nil?
49
+
50
+ return Date.parse(project.due_on) unless project.due_on.nil?
51
+
52
+ return nil
53
+ end
54
+
55
+ if field_name == :start
56
+ return Time.parse(project.start_at) unless project.start_at.nil?
57
+
58
+ return Date.parse(project.start_on) unless project.start_on.nil?
59
+
60
+ return nil
61
+ end
62
+
63
+ raise "Teach me how to handle field #{field_name}"
64
+ end
65
+
66
+ # @sg-ignore
67
+ # @param project [Asana::Resources::Project]
68
+ # @param custom_field_gid [String]
69
+ # @return [Hash]
70
+ def pull_custom_field_or_raise(project, custom_field_gid)
71
+ # @type [Array<Hash>]
72
+ custom_fields = project.custom_fields
73
+ if custom_fields.nil?
74
+ raise "Could not find custom_fields under project (was 'custom_fields' included in 'extra_fields'?)"
75
+ end
76
+
77
+ # @sg-ignore
78
+ # @type [Hash, nil]
79
+ matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid }
80
+ if matched_custom_field.nil?
81
+ raise "Could not find custom field with gid #{custom_field_gid} " \
82
+ "in project #{project.gid} with custom fields #{custom_fields}"
83
+ end
84
+
85
+ matched_custom_field
86
+ end
87
+
88
+ # @return [Array<(Symbol, Array)>]
89
+ attr_reader :selector
90
+
91
+ # @sg-ignore
92
+ # @param custom_field [Hash]
93
+ # @return [Array<String>]
94
+ def pull_enum_values(custom_field)
95
+ resource_subtype = custom_field.fetch('resource_subtype')
96
+ case resource_subtype
97
+ when 'enum'
98
+ [custom_field.fetch('enum_value')]
99
+ when 'multi_enum'
100
+ custom_field.fetch('multi_enum_values')
101
+ else
102
+ raise "Teach me how to handle resource_subtype #{resource_subtype}"
103
+ end
104
+ end
105
+
106
+ # @param custom_field [Hash]
107
+ # @param enum_value [Object, nil]
108
+ # @return [Array<String>]
109
+ def find_gids(custom_field, enum_value)
110
+ if enum_value.nil?
111
+ []
112
+ else
113
+ raise "Unexpected enabled value on custom field: #{custom_field}" if enum_value.fetch('enabled') == false
114
+
115
+ [enum_value.fetch('gid')]
116
+ end
117
+ end
118
+
119
+ # @param project [Asana::Resources::Project]
120
+ # @param custom_field_gid [String]
121
+ # @return [Array<String>]
122
+ def pull_custom_field_values_gids(project, custom_field_gid)
123
+ custom_field = pull_custom_field_or_raise(project, custom_field_gid)
124
+ pull_enum_values(custom_field).flat_map do |enum_value|
125
+ find_gids(custom_field, enum_value)
126
+ end
127
+ end
128
+
129
+ # @sg-ignore
130
+ # @param project [Asana::Resources::Project]
131
+ # @param custom_field_name [String]
132
+ # @return [Hash, nil]
133
+ def pull_custom_field_by_name(project, custom_field_name)
134
+ custom_fields = project.custom_fields
135
+ if custom_fields.nil?
136
+ raise "custom fields not found on project - did you add 'custom_fields' in your extra_fields argument?"
137
+ end
138
+
139
+ # @sg-ignore
140
+ # @type [Hash, nil]
141
+ custom_fields.find { |field| field.fetch('name') == custom_field_name }
142
+ end
143
+
144
+ # @param project [Asana::Resources::Project]
145
+ # @param custom_field_name [String]
146
+ # @return [Hash]
147
+ def pull_custom_field_by_name_or_raise(project, custom_field_name)
148
+ custom_field = pull_custom_field_by_name(project, custom_field_name)
149
+ if custom_field.nil?
150
+ raise "Could not find custom field with name #{custom_field_name} " \
151
+ "in project #{project.gid} with custom fields #{project.custom_fields}"
152
+ end
153
+ custom_field
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common/function_evaluator'
4
+
5
+ module Checkoff
6
+ module SelectorClasses
7
+ module Common
8
+ # :and function
9
+ class AndFunctionEvaluator < FunctionEvaluator
10
+ FUNCTION_NAME = :and
11
+
12
+ def matches?
13
+ fn?(selector, FUNCTION_NAME)
14
+ end
15
+
16
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
17
+ # @param lhs [Object]
18
+ # @param rhs [Object]
19
+ # @return [Object]
20
+ def evaluate(_resource, lhs, rhs)
21
+ lhs && rhs
22
+ end
23
+ end
24
+
25
+ # :or function
26
+ #
27
+ # Does not yet shortcut, but may in future - be careful with side
28
+ # effects!
29
+ class OrFunctionEvaluator < FunctionEvaluator
30
+ FUNCTION_NAME = :or
31
+
32
+ def matches?
33
+ fn?(selector, FUNCTION_NAME)
34
+ end
35
+
36
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
37
+ # @param lhs [Object]
38
+ # @param rhs [Object]
39
+ # @return [Object]
40
+ def evaluate(_resource, lhs, rhs)
41
+ lhs || rhs
42
+ end
43
+ end
44
+
45
+ # :not function
46
+ class NotFunctionEvaluator < FunctionEvaluator
47
+ FUNCTION_NAME = :not
48
+
49
+ def matches?
50
+ fn?(selector, FUNCTION_NAME)
51
+ end
52
+
53
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
54
+ # @param subvalue [Object]
55
+ # @return [Boolean]
56
+ def evaluate(_resource, subvalue)
57
+ !subvalue
58
+ end
59
+ end
60
+
61
+ # :nil? function
62
+ class NilPFunctionEvaluator < FunctionEvaluator
63
+ def matches?
64
+ fn?(selector, :nil?)
65
+ end
66
+
67
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
68
+ # @param subvalue [Object]
69
+ # @return [Boolean]
70
+ def evaluate(_resource, subvalue)
71
+ subvalue.nil?
72
+ end
73
+ end
74
+
75
+ # :equals? function
76
+ class EqualsPFunctionEvaluator < FunctionEvaluator
77
+ FUNCTION_NAME = :equals?
78
+
79
+ def matches?
80
+ fn?(selector, FUNCTION_NAME)
81
+ end
82
+
83
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
84
+ # @param lhs [Object]
85
+ # @param rhs [Object]
86
+ # @return [Boolean]
87
+ def evaluate(_resource, lhs, rhs)
88
+ lhs == rhs
89
+ end
90
+ end
91
+
92
+ # :custom_field_value function
93
+ class CustomFieldValueFunctionEvaluator < FunctionEvaluator
94
+ FUNCTION_NAME = :custom_field_value
95
+
96
+ def matches?
97
+ fn?(selector, FUNCTION_NAME)
98
+ end
99
+
100
+ # @param _index [Integer]
101
+ def evaluate_arg?(_index)
102
+ false
103
+ end
104
+
105
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
106
+ # @param custom_field_name [String]
107
+ # @return [String, nil]
108
+ def evaluate(resource, custom_field_name)
109
+ custom_field = pull_custom_field_by_name(resource, custom_field_name)
110
+ return nil if custom_field.nil?
111
+
112
+ custom_field['display_value']
113
+ end
114
+ end
115
+
116
+ # :custom_field_gid_value function
117
+ class CustomFieldGidValueFunctionEvaluator < FunctionEvaluator
118
+ def matches?
119
+ fn?(selector, :custom_field_gid_value)
120
+ end
121
+
122
+ def evaluate_arg?(_index)
123
+ false
124
+ end
125
+
126
+ # @sg-ignore
127
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
128
+ # @param custom_field_gid [String]
129
+ # @return [String, nil]
130
+ def evaluate(resource, custom_field_gid)
131
+ custom_field = pull_custom_field_or_raise(resource, custom_field_gid)
132
+ custom_field['display_value']
133
+ end
134
+ end
135
+
136
+ # :custom_field_gid_value_contains_any_gid function
137
+ class CustomFieldGidValueContainsAnyGidFunctionEvaluator < FunctionEvaluator
138
+ FUNCTION_NAME = :custom_field_gid_value_contains_any_gid
139
+
140
+ def matches?
141
+ fn?(selector, FUNCTION_NAME)
142
+ end
143
+
144
+ def evaluate_arg?(_index)
145
+ false
146
+ end
147
+
148
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
149
+ # @param custom_field_gid [String]
150
+ # @param custom_field_values_gids [Array<String>]
151
+ # @return [Boolean]
152
+ def evaluate(resource, custom_field_gid, custom_field_values_gids)
153
+ actual_custom_field_values_gids = pull_custom_field_values_gids(resource, custom_field_gid)
154
+
155
+ actual_custom_field_values_gids.any? do |custom_field_value|
156
+ custom_field_values_gids.include?(custom_field_value)
157
+ end
158
+ end
159
+ end
160
+
161
+ # :custom_field_value_contains_any_value
162
+ class CustomFieldValueContainsAnyValueFunctionEvaluator < FunctionEvaluator
163
+ FUNCTION_NAME = :custom_field_value_contains_any_value
164
+
165
+ def matches?
166
+ fn?(selector, FUNCTION_NAME)
167
+ end
168
+
169
+ def evaluate_arg?(_index)
170
+ false
171
+ end
172
+
173
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
174
+ # @param custom_field_name [String]
175
+ # @param custom_field_values [Array<String>]
176
+ # @return [Boolean]
177
+ def evaluate(resource, custom_field_name, custom_field_values)
178
+ custom_field = pull_custom_field_by_name(resource, custom_field_name)
179
+
180
+ return false if custom_field.nil?
181
+
182
+ custom_field_values.include?(custom_field.fetch('display_value'))
183
+ end
184
+ end
185
+
186
+ # :custom_field_gid_value_contains_all_gids function
187
+ class CustomFieldGidValueContainsAllGidsFunctionEvaluator < FunctionEvaluator
188
+ FUNCTION_NAME = :custom_field_gid_value_contains_all_gids
189
+
190
+ def matches?
191
+ fn?(selector, FUNCTION_NAME)
192
+ end
193
+
194
+ def evaluate_arg?(_index)
195
+ false
196
+ end
197
+
198
+ # @param resource [Asana::Resources::Task,Asana::Resources::Project]
199
+ # @param custom_field_gid [String]
200
+ # @param custom_field_values_gids [Array<String>]
201
+ # @return [Boolean]
202
+ def evaluate(resource, custom_field_gid, custom_field_values_gids)
203
+ actual_custom_field_values_gids = pull_custom_field_values_gids(resource, custom_field_gid)
204
+
205
+ custom_field_values_gids.all? do |custom_field_value|
206
+ actual_custom_field_values_gids.include?(custom_field_value)
207
+ end
208
+ end
209
+ end
210
+
211
+ # String literals
212
+ class StringLiteralEvaluator < FunctionEvaluator
213
+ def matches?
214
+ selector.is_a?(String)
215
+ end
216
+
217
+ # @sg-ignore
218
+ # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
219
+ # @return [String]
220
+ def evaluate(_resource)
221
+ selector
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkoff
4
+ module SelectorClasses
5
+ # Base class to evaluate types of selector functions
6
+ class FunctionEvaluator
7
+ # @sg-ignore
8
+ # @param _index [Integer]
9
+ def evaluate_arg?(_index)
10
+ true
11
+ end
12
+
13
+ # @sg-ignore
14
+ # @return [Boolean]
15
+ def matches?
16
+ raise 'Override me!'
17
+ end
18
+
19
+ # @param _task [Asana::Resources::Task]
20
+ # @param _args [Array<Object>]
21
+ # @return [Object]
22
+ # @sg-ignore
23
+ def evaluate(_task, *_args)
24
+ raise 'Implement me!'
25
+ end
26
+
27
+ private
28
+
29
+ # @param object [Object]
30
+ # @param fn_name [Symbol]
31
+ def fn?(object, fn_name)
32
+ object.is_a?(Array) && !object.empty? && [fn_name, fn_name.to_s].include?(object[0])
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../function_evaluator'
4
+
5
+ module Checkoff
6
+ module SelectorClasses
7
+ module Project
8
+ # Base class to evaluate a project selector function given fully evaluated arguments
9
+ class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
10
+ # @param selector [Array<(Symbol, Array)>,String]
11
+ # @param projects [Checkoff::Projects]
12
+ def initialize(selector:,
13
+ projects:)
14
+ @selector = selector
15
+ @projects = projects
16
+ super()
17
+ end
18
+
19
+ private
20
+
21
+ # @param project [Asana::Resources::Project]
22
+ # @param field_name [Symbol]
23
+ #
24
+ # @sg-ignore
25
+ # @return [Date, nil]
26
+ def pull_date_field_by_name(project, field_name)
27
+ if field_name == :modified
28
+ return Time.parse(project.modified_at).to_date unless project.modified_at.nil?
29
+
30
+ return nil
31
+ end
32
+
33
+ if field_name == :due
34
+ return Time.parse(project.due_at).to_date unless project.due_at.nil?
35
+
36
+ return Date.parse(project.due_on) unless project.due_on.nil?
37
+
38
+ return nil
39
+ end
40
+
41
+ raise "Teach me how to handle field #{field_name}"
42
+ end
43
+
44
+ # @param project [Asana::Resources::Project]
45
+ # @param field_name [Symbol]
46
+ #
47
+ # @sg-ignore
48
+ # @return [Date, Time, nil]
49
+ def pull_date_or_time_field_by_name(project, field_name)
50
+ if field_name == :due
51
+ return Time.parse(project.due_at) unless project.due_at.nil?
52
+
53
+ return Date.parse(project.due_on) unless project.due_on.nil?
54
+
55
+ return nil
56
+ end
57
+
58
+ if field_name == :start
59
+ return Time.parse(project.start_at) unless project.start_at.nil?
60
+
61
+ return Date.parse(project.start_on) unless project.start_on.nil?
62
+
63
+ return nil
64
+ end
65
+
66
+ raise "Teach me how to handle field #{field_name}"
67
+ end
68
+
69
+ # @sg-ignore
70
+ # @param project [Asana::Resources::Project]
71
+ # @param custom_field_gid [String]
72
+ # @return [Hash]
73
+ def pull_custom_field_or_raise(project, custom_field_gid)
74
+ # @type [Array<Hash>]
75
+ custom_fields = project.custom_fields
76
+ if custom_fields.nil?
77
+ raise "Could not find custom_fields under project (was 'custom_fields' included in 'extra_fields'?)"
78
+ end
79
+
80
+ # @sg-ignore
81
+ # @type [Hash, nil]
82
+ matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid }
83
+ if matched_custom_field.nil?
84
+ raise "Could not find custom field with gid #{custom_field_gid} " \
85
+ "in project #{project.gid} with custom fields #{custom_fields}"
86
+ end
87
+
88
+ matched_custom_field
89
+ end
90
+
91
+ # @return [Array<(Symbol, Array)>]
92
+ attr_reader :selector
93
+
94
+ # @sg-ignore
95
+ # @param custom_field [Hash]
96
+ # @return [Array<String>]
97
+ def pull_enum_values(custom_field)
98
+ resource_subtype = custom_field.fetch('resource_subtype')
99
+ case resource_subtype
100
+ when 'enum'
101
+ [custom_field.fetch('enum_value')]
102
+ when 'multi_enum'
103
+ custom_field.fetch('multi_enum_values')
104
+ else
105
+ raise "Teach me how to handle resource_subtype #{resource_subtype}"
106
+ end
107
+ end
108
+
109
+ # @param custom_field [Hash]
110
+ # @param enum_value [Object, nil]
111
+ # @return [Array<String>]
112
+ def find_gids(custom_field, enum_value)
113
+ if enum_value.nil?
114
+ []
115
+ else
116
+ raise "Unexpected enabled value on custom field: #{custom_field}" if enum_value.fetch('enabled') == false
117
+
118
+ [enum_value.fetch('gid')]
119
+ end
120
+ end
121
+
122
+ # @param project [Asana::Resources::Project]
123
+ # @param custom_field_gid [String]
124
+ # @return [Array<String>]
125
+ def pull_custom_field_values_gids(project, custom_field_gid)
126
+ custom_field = pull_custom_field_or_raise(project, custom_field_gid)
127
+ pull_enum_values(custom_field).flat_map do |enum_value|
128
+ find_gids(custom_field, enum_value)
129
+ end
130
+ end
131
+
132
+ # @sg-ignore
133
+ # @param project [Asana::Resources::Project]
134
+ # @param custom_field_name [String]
135
+ # @return [Hash, nil]
136
+ def pull_custom_field_by_name(project, custom_field_name)
137
+ custom_fields = project.custom_fields
138
+ if custom_fields.nil?
139
+ raise "custom fields not found on project - did you add 'custom_fields' in your extra_fields argument?"
140
+ end
141
+
142
+ # @sg-ignore
143
+ # @type [Hash, nil]
144
+ custom_fields.find { |field| field.fetch('name') == custom_field_name }
145
+ end
146
+
147
+ # @param project [Asana::Resources::Project]
148
+ # @param custom_field_name [String]
149
+ # @return [Hash]
150
+ def pull_custom_field_by_name_or_raise(project, custom_field_name)
151
+ custom_field = pull_custom_field_by_name(project, custom_field_name)
152
+ if custom_field.nil?
153
+ raise "Could not find custom field with name #{custom_field_name} " \
154
+ "in project #{project.gid} with custom fields #{project.custom_fields}"
155
+ end
156
+ custom_field
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'project/function_evaluator'
4
+
5
+ module Checkoff
6
+ module SelectorClasses
7
+ # Project selector classes
8
+ module Project; end
9
+ end
10
+ end