checkoff 0.58.1 → 0.60.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: 560f8d3dadb4f15e7b3329091bd82e301b23dd479f558e1a637a04aa8bc2067b
4
+ data.tar.gz: bdfbe8d442602eaa15bb0c995677d88ad4897b8b8f068f04e087ca28ed02d255
5
5
  SHA512:
6
- metadata.gz: 2e77e9c93328415564d60bc1b3b1ca29bb678ce425477137392acd26d92acd81823a9fa9e6db189ce2a22045ca6dc45df32a8548a9265889e95f86196d238b1e
7
- data.tar.gz: 516063b25c108b17ac6039c3890cf375a623078b913a33fecb019ae4c9a4f76e2a1f3995fae4cb3c529d5dddcb2d9076f5ec07e940a464ed13330c5b63ab3fbd
6
+ metadata.gz: 5044cbdbe5d31947c795bdf3ca9d90ab71614059779c1a3ac3a3c19430a227ca89a1ee9ff661b3c523eef9cc9b019843aae41c4240d321b91725efc7dacc1731
7
+ data.tar.gz: ab7bf654fdd30941dfe00ad78351b6b5aa36275b36103b491954f953cf10a643a16d6895cb8c091db52b3586ea3fbe7c12f0be94beecd2498a83242a07d781ab
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.60.0)
16
16
  activesupport
17
17
  asana (> 0.10.0)
18
18
  cache_method
@@ -22,17 +22,24 @@ PATH
22
22
  GEM
23
23
  remote: https://rubygems.org/
24
24
  specs:
25
- activesupport (7.0.8)
25
+ activesupport (7.1.0)
26
+ base64
27
+ bigdecimal
26
28
  concurrent-ruby (~> 1.0, >= 1.0.2)
29
+ connection_pool (>= 2.2.5)
30
+ drb
27
31
  i18n (>= 1.6, < 2)
28
32
  minitest (>= 5.1)
33
+ mutex_m
29
34
  tzinfo (~> 2.0)
30
35
  addressable (2.8.1)
31
36
  public_suffix (>= 2.0.2, < 6.0)
32
37
  ansi (1.5.0)
33
38
  ast (2.4.2)
34
39
  backport (1.2.0)
40
+ base64 (0.1.1)
35
41
  benchmark (0.2.1)
42
+ bigdecimal (3.1.4)
36
43
  builder (3.2.4)
37
44
  bump (0.10.0)
38
45
  cache (0.4.1)
@@ -43,11 +50,14 @@ GEM
43
50
  childprocess (4.1.0)
44
51
  coderay (1.1.3)
45
52
  concurrent-ruby (1.2.2)
53
+ connection_pool (2.4.1)
46
54
  crack (0.4.5)
47
55
  rexml
48
56
  dalli (3.2.6)
49
57
  diff-lcs (1.5.0)
50
58
  docile (1.4.0)
59
+ drb (2.1.1)
60
+ ruby2_keywords
51
61
  e2mmap (0.1.0)
52
62
  fakeweb (1.3.0)
53
63
  faraday (1.5.1)
@@ -110,6 +120,7 @@ GEM
110
120
  multi_json (1.15.0)
111
121
  multi_xml (0.6.0)
112
122
  multipart-post (2.1.1)
123
+ mutex_m (0.1.2)
113
124
  nokogiri (1.15.2)
114
125
  mini_portile2 (~> 2.8.2)
115
126
  racc (~> 1.4)
@@ -0,0 +1,46 @@
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
+ COMMON_FUNCTION_EVALUATORS = (Checkoff::SelectorClasses::Common.constants.map do |const|
22
+ Checkoff::SelectorClasses::Common.const_get(const)
23
+ end - [Checkoff::SelectorClasses::Common::FunctionEvaluator]).freeze
24
+
25
+ PROJECT_FUNCTION_EVALUATORS = (Checkoff::SelectorClasses::Project.constants.map do |const|
26
+ Checkoff::SelectorClasses::Project.const_get(const)
27
+ end - [Checkoff::SelectorClasses::Project::FunctionEvaluator]).freeze
28
+
29
+ FUNCTION_EVALUTORS = (COMMON_FUNCTION_EVALUATORS + PROJECT_FUNCTION_EVALUATORS).freeze
30
+
31
+ # @return [Array<Class<ProjectSelectorClasses::FunctionEvaluator>>]
32
+ def function_evaluators
33
+ FUNCTION_EVALUTORS
34
+ end
35
+
36
+ # @return [Hash]
37
+ def initializer_kwargs
38
+ { projects: projects }
39
+ end
40
+
41
+ # @return [Asana::Resources::Project]
42
+ attr_reader :item
43
+ # @return [Checkoff::Projects]
44
+ attr_reader :projects
45
+ end
46
+ end
@@ -70,6 +70,14 @@ module Checkoff
70
70
  end
71
71
  end
72
72
 
73
+ # Handle 'portfolios.ids' search url param
74
+ class PortfoliosIds < SimpleParam
75
+ # @return [Array<String>]
76
+ def convert
77
+ { 'portfolios.any' => single_value.split('~').join(',') }.to_a.flatten
78
+ end
79
+ end
80
+
73
81
  # Handle 'any_projects.ids' search url param
74
82
  class AnyProjectsIds < SimpleParam
75
83
  # @return [Array<String>]
@@ -188,6 +196,7 @@ module Checkoff
188
196
 
189
197
  # @type [Hash<String, Class<SimpleParam::SimpleParam>>] the mapping from param name to class
190
198
  ARGS = {
199
+ 'portfolios.ids' => SimpleParam::PortfoliosIds,
191
200
  'any_projects.ids' => SimpleParam::AnyProjectsIds,
192
201
  'not_projects.ids' => SimpleParam::NotProjectsIds,
193
202
  'completion' => SimpleParam::Completion,
@@ -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