checkoff 0.40.0 → 0.42.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/checkoff/internal/config_loader.rb +1 -0
- data/lib/checkoff/internal/task_selector_evaluator.rb +180 -16
- data/lib/checkoff/task_searches.rb +0 -8
- data/lib/checkoff/task_selectors.rb +10 -2
- data/lib/checkoff/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83cd30ff493ada586596020db325eed63c9e4c24919e34c7e9926c0f9bedf51f
|
4
|
+
data.tar.gz: fee54feaa76dafc06dba69267f4ea6a7b2877e68df6af43738468e65cca552b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d4cb677d35d91830b2aa6145d40a74ebb1e34ddd9a1e17203ef62ac66afb30a3a864924462de263ff663d33cc561272143fd1b0ab45e9499c4f42e2dad97840
|
7
|
+
data.tar.gz: b074ac5d6a2675e801b52e96d8453847607bf0e523a5988a50456258f1a668319846c8c1f965eadb09409f83b82d23b1262935c2606bab175de0664c7af2077a
|
data/Gemfile.lock
CHANGED
@@ -3,28 +3,47 @@
|
|
3
3
|
module Checkoff
|
4
4
|
# Base class to evaluate a task selector function given fully evaluated arguments
|
5
5
|
class FunctionEvaluator
|
6
|
+
# @param task_selector [Array<(Symbol, Array)>,String]
|
7
|
+
# @param tasks [Checkoff::Tasks]
|
6
8
|
def initialize(task_selector:,
|
7
9
|
tasks:)
|
8
10
|
@task_selector = task_selector
|
9
11
|
@tasks = tasks
|
10
12
|
end
|
11
13
|
|
14
|
+
# @sg-ignore
|
15
|
+
# @param _index [Integer]
|
12
16
|
def evaluate_arg?(_index)
|
13
17
|
true
|
14
18
|
end
|
15
19
|
|
20
|
+
# @sg-ignore
|
21
|
+
# @return [Boolean]
|
22
|
+
def matches?
|
23
|
+
raise 'Override me!'
|
24
|
+
end
|
25
|
+
|
16
26
|
private
|
17
27
|
|
28
|
+
# @param object [Object]
|
29
|
+
# @param fn_name [Symbol]
|
18
30
|
def fn?(object, fn_name)
|
19
31
|
object.is_a?(Array) && !object.empty? && [fn_name, fn_name.to_s].include?(object[0])
|
20
32
|
end
|
21
33
|
|
34
|
+
# @sg-ignore
|
35
|
+
# @param task [Asana::Resources::Task]
|
36
|
+
# @param custom_field_gid [String]
|
37
|
+
# @return [Hash]
|
22
38
|
def pull_custom_field_or_raise(task, custom_field_gid)
|
39
|
+
# @type [Array<Hash>]
|
23
40
|
custom_fields = task.custom_fields
|
24
41
|
if custom_fields.nil?
|
25
42
|
raise "Could not find custom_fields under task (was 'custom_fields' included in 'extra_fields'?)"
|
26
43
|
end
|
27
44
|
|
45
|
+
# @sg-ignore
|
46
|
+
# @type [Hash, nil]
|
28
47
|
matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid }
|
29
48
|
if matched_custom_field.nil?
|
30
49
|
raise "Could not find custom field with gid #{custom_field_gid} " \
|
@@ -34,8 +53,12 @@ module Checkoff
|
|
34
53
|
matched_custom_field
|
35
54
|
end
|
36
55
|
|
56
|
+
# @return [Array<(Symbol, Array)>]
|
37
57
|
attr_reader :task_selector
|
38
58
|
|
59
|
+
# @sg-ignore
|
60
|
+
# @param custom_field [Hash]
|
61
|
+
# @return [Array<String>]
|
39
62
|
def pull_enum_values(custom_field)
|
40
63
|
resource_subtype = custom_field.fetch('resource_subtype')
|
41
64
|
case resource_subtype
|
@@ -48,6 +71,9 @@ module Checkoff
|
|
48
71
|
end
|
49
72
|
end
|
50
73
|
|
74
|
+
# @param custom_field [Hash]
|
75
|
+
# @param enum_value [Object, nil]
|
76
|
+
# @return [Array<String>]
|
51
77
|
def find_gids(custom_field, enum_value)
|
52
78
|
if enum_value.nil?
|
53
79
|
[]
|
@@ -58,12 +84,42 @@ module Checkoff
|
|
58
84
|
end
|
59
85
|
end
|
60
86
|
|
87
|
+
# @param task [Asana::Resources::Task]
|
88
|
+
# @param custom_field_gid [String]
|
89
|
+
# @return [Array<String>]
|
61
90
|
def pull_custom_field_values_gids(task, custom_field_gid)
|
62
91
|
custom_field = pull_custom_field_or_raise(task, custom_field_gid)
|
63
92
|
pull_enum_values(custom_field).flat_map do |enum_value|
|
64
93
|
find_gids(custom_field, enum_value)
|
65
94
|
end
|
66
95
|
end
|
96
|
+
|
97
|
+
# @sg-ignore
|
98
|
+
# @param task [Asana::Resources::Task]
|
99
|
+
# @param custom_field_name [String]
|
100
|
+
# @return [Hash, nil]
|
101
|
+
def pull_custom_field_by_name(task, custom_field_name)
|
102
|
+
custom_fields = task.custom_fields
|
103
|
+
if custom_fields.nil?
|
104
|
+
raise "custom fields not found on task - did you add 'custom_field' in your extra_fields argument?"
|
105
|
+
end
|
106
|
+
|
107
|
+
# @sg-ignore
|
108
|
+
# @type [Hash, nil]
|
109
|
+
custom_fields.find { |field| field.fetch('name') == custom_field_name }
|
110
|
+
end
|
111
|
+
|
112
|
+
# @param task [Asana::Resources::Task]
|
113
|
+
# @param custom_field_name [String]
|
114
|
+
# @return [Hash]
|
115
|
+
def pull_custom_field_by_name_or_raise(task, custom_field_name)
|
116
|
+
custom_field = pull_custom_field_by_name(task, custom_field_name)
|
117
|
+
if custom_field.nil?
|
118
|
+
raise "Could not find custom field with name #{custom_field_name} " \
|
119
|
+
"in task #{task.gid} with custom fields #{task.custom_fields}"
|
120
|
+
end
|
121
|
+
custom_field
|
122
|
+
end
|
67
123
|
end
|
68
124
|
|
69
125
|
# :and function
|
@@ -72,6 +128,10 @@ module Checkoff
|
|
72
128
|
fn?(task_selector, :and)
|
73
129
|
end
|
74
130
|
|
131
|
+
# @param _task [Asana::Resources::Task]
|
132
|
+
# @param lhs [Object]
|
133
|
+
# @param rhs [Object]
|
134
|
+
# @return [Object]
|
75
135
|
def evaluate(_task, lhs, rhs)
|
76
136
|
lhs && rhs
|
77
137
|
end
|
@@ -83,6 +143,9 @@ module Checkoff
|
|
83
143
|
fn?(task_selector, :not)
|
84
144
|
end
|
85
145
|
|
146
|
+
# @param _task [Asana::Resources::Task]
|
147
|
+
# @param subvalue [Object]
|
148
|
+
# @return [Boolean]
|
86
149
|
def evaluate(_task, subvalue)
|
87
150
|
!subvalue
|
88
151
|
end
|
@@ -94,21 +157,44 @@ module Checkoff
|
|
94
157
|
fn?(task_selector, :nil?)
|
95
158
|
end
|
96
159
|
|
160
|
+
# @param _task [Asana::Resources::Task]
|
161
|
+
# @param subvalue [Object]
|
162
|
+
# @return [Boolean]
|
97
163
|
def evaluate(_task, subvalue)
|
98
164
|
subvalue.nil?
|
99
165
|
end
|
100
166
|
end
|
101
167
|
|
168
|
+
# :equals? function
|
169
|
+
class EqualsPFunctionEvaluator < FunctionEvaluator
|
170
|
+
def matches?
|
171
|
+
fn?(task_selector, :equals?)
|
172
|
+
end
|
173
|
+
|
174
|
+
# @param _task [Asana::Resources::Task]
|
175
|
+
# @param lhs [Object]
|
176
|
+
# @param rhs [Object]
|
177
|
+
# @return [Boolean]
|
178
|
+
def evaluate(_task, lhs, rhs)
|
179
|
+
lhs == rhs
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
102
183
|
# :tag function
|
103
184
|
class TagPFunctionEvaluator < FunctionEvaluator
|
104
185
|
def matches?
|
105
186
|
fn?(task_selector, :tag)
|
106
187
|
end
|
107
188
|
|
189
|
+
# @param _index [Integer]
|
108
190
|
def evaluate_arg?(_index)
|
109
191
|
false
|
110
192
|
end
|
111
193
|
|
194
|
+
# @sg-ignore
|
195
|
+
# @param task [Asana::Resources::Task]
|
196
|
+
# @param tag_name [String]
|
197
|
+
# @return [Boolean]
|
112
198
|
def evaluate(task, tag_name)
|
113
199
|
task.tags.map(&:name).include? tag_name
|
114
200
|
end
|
@@ -120,6 +206,8 @@ module Checkoff
|
|
120
206
|
fn?(task_selector, :due)
|
121
207
|
end
|
122
208
|
|
209
|
+
# @param task [Asana::Resources::Task]
|
210
|
+
# @return [Boolean]
|
123
211
|
def evaluate(task)
|
124
212
|
@tasks.task_ready?(task)
|
125
213
|
end
|
@@ -131,6 +219,9 @@ module Checkoff
|
|
131
219
|
fn?(task_selector, :due_date_set)
|
132
220
|
end
|
133
221
|
|
222
|
+
# @sg-ignore
|
223
|
+
# @param task [Asana::Resources::Task]
|
224
|
+
# @return [Boolean]
|
134
225
|
def evaluate(task)
|
135
226
|
!task.due_at.nil? || !task.due_on.nil?
|
136
227
|
end
|
@@ -142,17 +233,16 @@ module Checkoff
|
|
142
233
|
fn?(task_selector, :custom_field_value)
|
143
234
|
end
|
144
235
|
|
236
|
+
# @param _index [Integer]
|
145
237
|
def evaluate_arg?(_index)
|
146
238
|
false
|
147
239
|
end
|
148
240
|
|
241
|
+
# @param task [Asana::Resources::Task]
|
242
|
+
# @param custom_field_name [String]
|
243
|
+
# @return [String, nil]
|
149
244
|
def evaluate(task, custom_field_name)
|
150
|
-
|
151
|
-
if custom_fields.nil?
|
152
|
-
raise "custom fields not found on task - did you add 'custom_field' in your extra_fields argument?"
|
153
|
-
end
|
154
|
-
|
155
|
-
custom_field = custom_fields.find { |field| field.fetch('name') == custom_field_name }
|
245
|
+
custom_field = pull_custom_field_by_name(task, custom_field_name)
|
156
246
|
return nil if custom_field.nil?
|
157
247
|
|
158
248
|
custom_field['display_value']
|
@@ -169,6 +259,10 @@ module Checkoff
|
|
169
259
|
false
|
170
260
|
end
|
171
261
|
|
262
|
+
# @sg-ignore
|
263
|
+
# @param task [Asana::Resources::Task]
|
264
|
+
# @param custom_field_gid [String]
|
265
|
+
# @return [String, nil]
|
172
266
|
def evaluate(task, custom_field_gid)
|
173
267
|
custom_field = pull_custom_field_or_raise(task, custom_field_gid)
|
174
268
|
custom_field['display_value']
|
@@ -185,6 +279,10 @@ module Checkoff
|
|
185
279
|
false
|
186
280
|
end
|
187
281
|
|
282
|
+
# @param task [Asana::Resources::Task]
|
283
|
+
# @param custom_field_gid [String]
|
284
|
+
# @param custom_field_values_gids [Array<String>]
|
285
|
+
# @return [Boolean]
|
188
286
|
def evaluate(task, custom_field_gid, custom_field_values_gids)
|
189
287
|
actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid)
|
190
288
|
|
@@ -204,6 +302,10 @@ module Checkoff
|
|
204
302
|
false
|
205
303
|
end
|
206
304
|
|
305
|
+
# @param task [Asana::Resources::Task]
|
306
|
+
# @param custom_field_gid [String]
|
307
|
+
# @param custom_field_values_gids [Array<String>]
|
308
|
+
# @return [Boolean]
|
207
309
|
def evaluate(task, custom_field_gid, custom_field_values_gids)
|
208
310
|
actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid)
|
209
311
|
|
@@ -213,8 +315,48 @@ module Checkoff
|
|
213
315
|
end
|
214
316
|
end
|
215
317
|
|
318
|
+
# :custom_field_less_than_n_days_from_now function
|
319
|
+
class CustomFieldLessThanNDaysFromNowFunctionEvaluator < FunctionEvaluator
|
320
|
+
def matches?
|
321
|
+
fn?(task_selector, :custom_field_less_than_n_days_from_now)
|
322
|
+
end
|
323
|
+
|
324
|
+
def evaluate_arg?(_index)
|
325
|
+
false
|
326
|
+
end
|
327
|
+
|
328
|
+
# @param task [Asana::Resources::Task]
|
329
|
+
# @param custom_field_name [String]
|
330
|
+
# @param num_days [Integer]
|
331
|
+
# @return [Boolean]
|
332
|
+
def evaluate(task, custom_field_name, num_days)
|
333
|
+
custom_field = pull_custom_field_by_name_or_raise(task, custom_field_name)
|
334
|
+
|
335
|
+
time_str = custom_field.fetch('display_value')
|
336
|
+
time = Time.parse(time_str)
|
337
|
+
n_days_from_now = (Time.now + (num_days * 24 * 60 * 60))
|
338
|
+
time < n_days_from_now
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# String literals
|
343
|
+
class StringLiteralEvaluator < FunctionEvaluator
|
344
|
+
def matches?
|
345
|
+
task_selector.is_a?(String)
|
346
|
+
end
|
347
|
+
|
348
|
+
# @sg-ignore
|
349
|
+
# @param _task [Asana::Resources::Task]
|
350
|
+
# @return [String]
|
351
|
+
def evaluate(_task)
|
352
|
+
task_selector
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
216
356
|
# Evaluator task selectors against a task
|
217
357
|
class TaskSelectorEvaluator
|
358
|
+
# @param task [Asana::Resources::Task]
|
359
|
+
# @param tasks [Checkoff::Tasks]
|
218
360
|
def initialize(task:,
|
219
361
|
tasks: Checkoff::Tasks.new)
|
220
362
|
@task = task
|
@@ -224,6 +366,7 @@ module Checkoff
|
|
224
366
|
FUNCTION_EVALUTORS = [
|
225
367
|
NotFunctionEvaluator,
|
226
368
|
NilPFunctionEvaluator,
|
369
|
+
EqualsPFunctionEvaluator,
|
227
370
|
TagPFunctionEvaluator,
|
228
371
|
CustomFieldValueFunctionEvaluator,
|
229
372
|
CustomFieldGidValueFunctionEvaluator,
|
@@ -232,12 +375,19 @@ module Checkoff
|
|
232
375
|
AndFunctionEvaluator,
|
233
376
|
DuePFunctionEvaluator,
|
234
377
|
DueDateSetPFunctionEvaluator,
|
378
|
+
CustomFieldLessThanNDaysFromNowFunctionEvaluator,
|
379
|
+
StringLiteralEvaluator,
|
235
380
|
].freeze
|
236
381
|
|
382
|
+
# @param task_selector [Array]
|
383
|
+
# @return [Boolean, Object, nil]
|
237
384
|
def evaluate(task_selector)
|
238
|
-
return true if task_selector
|
385
|
+
return true if task_selector.empty?
|
239
386
|
|
387
|
+
# @param evaluator_class [Class<FunctionEvaluator>]
|
240
388
|
FUNCTION_EVALUTORS.each do |evaluator_class|
|
389
|
+
# @sg-ignore
|
390
|
+
# @type [FunctionEvaluator]
|
241
391
|
evaluator = evaluator_class.new(task_selector: task_selector,
|
242
392
|
tasks: tasks)
|
243
393
|
|
@@ -246,23 +396,37 @@ module Checkoff
|
|
246
396
|
return try_this_evaluator(task_selector, evaluator)
|
247
397
|
end
|
248
398
|
|
249
|
-
raise "Syntax issue trying to handle #{task_selector}"
|
399
|
+
raise "Syntax issue trying to handle #{task_selector.inspect}"
|
250
400
|
end
|
251
401
|
|
252
402
|
private
|
253
403
|
|
404
|
+
# @sg-ignore
|
405
|
+
# @param task_selector [Array]
|
406
|
+
# @param evaluator [FunctionEvaluator]
|
407
|
+
# @return [Boolean, Object, nil]
|
254
408
|
def try_this_evaluator(task_selector, evaluator)
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
409
|
+
# if task_selector is an array
|
410
|
+
evaluated_args = if task_selector.is_a?(Array)
|
411
|
+
task_selector[1..].map.with_index do |item, index|
|
412
|
+
if evaluator.evaluate_arg?(index)
|
413
|
+
evaluate(item)
|
414
|
+
else
|
415
|
+
item
|
416
|
+
end
|
417
|
+
end
|
418
|
+
else
|
419
|
+
[]
|
420
|
+
end
|
262
421
|
|
263
422
|
evaluator.evaluate(task, *evaluated_args)
|
264
423
|
end
|
265
424
|
|
266
|
-
|
425
|
+
# @return [Asana::Resources::Task]
|
426
|
+
attr_reader :task
|
427
|
+
# @return [Checkoff::Tasks]
|
428
|
+
attr_reader :tasks
|
429
|
+
# @return [Array<(Symbol, Array)>]
|
430
|
+
attr_reader :task_selector
|
267
431
|
end
|
268
432
|
end
|
@@ -19,12 +19,9 @@ module Checkoff
|
|
19
19
|
# Run task searches against the Asana API
|
20
20
|
class TaskSearches
|
21
21
|
MINUTE = 60
|
22
|
-
# @sg-ignore
|
23
22
|
HOUR = MINUTE * 60
|
24
23
|
DAY = 24 * HOUR
|
25
|
-
# @sg-ignore
|
26
24
|
REALLY_LONG_CACHE_TIME = HOUR * 1
|
27
|
-
# @sg-ignore
|
28
25
|
LONG_CACHE_TIME = MINUTE * 15
|
29
26
|
SHORT_CACHE_TIME = MINUTE
|
30
27
|
|
@@ -62,7 +59,6 @@ module Checkoff
|
|
62
59
|
client: client)
|
63
60
|
tasks.select { |task| task_selectors.filter_via_task_selector(task, task_selector) }
|
64
61
|
end
|
65
|
-
# @sg-ignore
|
66
62
|
cache_method :task_search, LONG_CACHE_TIME
|
67
63
|
|
68
64
|
private
|
@@ -81,11 +77,7 @@ module Checkoff
|
|
81
77
|
# :nocov:
|
82
78
|
class << self
|
83
79
|
def run
|
84
|
-
# @sg-ignore
|
85
|
-
# @type [String]
|
86
80
|
workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
|
87
|
-
# @sg-ignore
|
88
|
-
# @type [String]
|
89
81
|
url = ARGV[1] || raise('Please pass task search URL as second argument')
|
90
82
|
task_searches = Checkoff::TaskSearches.new
|
91
83
|
task_search = task_searches.task_search(workspace_name, url)
|
@@ -21,6 +21,10 @@ module Checkoff
|
|
21
21
|
LONG_CACHE_TIME = MINUTE * 15
|
22
22
|
SHORT_CACHE_TIME = MINUTE
|
23
23
|
|
24
|
+
# @sg-ignore
|
25
|
+
# @param [Hash] config
|
26
|
+
# @param [Asana::Client] client
|
27
|
+
# @param [Checkoff::Tasks] tasks
|
24
28
|
def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
|
25
29
|
client: Checkoff::Clients.new(config: config).client,
|
26
30
|
tasks: Checkoff::Tasks.new(config: config,
|
@@ -30,8 +34,8 @@ module Checkoff
|
|
30
34
|
end
|
31
35
|
|
32
36
|
# @param [Asana::Resources::Task] task
|
33
|
-
# @param [
|
34
|
-
#
|
37
|
+
# @param [Array<(Symbol, Array)>] task_selector Filter based on
|
38
|
+
# task details. Examples: [:tag, 'foo'] [:not, [:tag, 'foo']] [:tag, 'foo']
|
35
39
|
# @return [Boolean]
|
36
40
|
def filter_via_task_selector(task, task_selector)
|
37
41
|
evaluator = TaskSelectorEvaluator.new(task: task, tasks: tasks)
|
@@ -40,16 +44,19 @@ module Checkoff
|
|
40
44
|
|
41
45
|
private
|
42
46
|
|
47
|
+
# @return [Checkoff::Tasks]
|
43
48
|
attr_reader :tasks
|
44
49
|
|
45
50
|
# bundle exec ./task_selectors.rb
|
46
51
|
# :nocov:
|
47
52
|
class << self
|
53
|
+
# @sg-ignore
|
48
54
|
# @return [String]
|
49
55
|
def project_name
|
50
56
|
ARGV[1] || raise('Please pass project name to pull tasks from as first argument')
|
51
57
|
end
|
52
58
|
|
59
|
+
# @sg-ignore
|
53
60
|
# @return [String]
|
54
61
|
def workspace_name
|
55
62
|
ARGV[0] || raise('Please pass workspace name as first argument')
|
@@ -61,6 +68,7 @@ module Checkoff
|
|
61
68
|
JSON.parse(task_selector_json)
|
62
69
|
end
|
63
70
|
|
71
|
+
# @return [void]
|
64
72
|
def run
|
65
73
|
require 'checkoff/projects'
|
66
74
|
|
data/lib/checkoff/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: checkoff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.42.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vince Broz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|