checkoff 0.58.1 → 0.59.0

Sign up to get free protection for your applications and to get access to all the features.
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