checkoff 0.56.0 → 0.58.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: 48f7926e53c17c01d34979e6e179e7de9f9c3f75e4b525ad198c12c774d2187e
4
- data.tar.gz: faba1965ff8947718c5118a30d4271a1ca8445ff43722f3fe0cfc77b0eb25e38
3
+ metadata.gz: e5b1efd75edc932f14b7c620a0c90ae36bb94a3734c1c6f8ee329301e6f50af8
4
+ data.tar.gz: 0fe857d53e21d4f66598104917c31fa455eeedc74f0e4e6aa6dc2a24f811c65a
5
5
  SHA512:
6
- metadata.gz: 396211f82f8e9636af8c46ee4049a7a5fae6278df3bd95f41bfb7350ca5cfe12fcc45544dac8800a4a2d7a2a8fd811a787657dad1d9c311c8fa1cdc989ba6d5a
7
- data.tar.gz: 81fa41307abbb32f576411fa147cbbf845eb341e086ab8697b2c715e4dd756b40709e84f471881ef0e192f77fce8f80beecc56586d3abad395a979de9078f2b7
6
+ metadata.gz: 84b7f631a32dd256986d85fb132226075857339552f0b9b4a880cb17a0f0378e307d8a323beac840b7b2a0a62aad92283cc817dcad1622bf329462adbbfc619c
7
+ data.tar.gz: af485a9d86625ed4f138a2bc466e506686c9660af08178dba3f82750adad7a1d4f8379ba434707ef10f78b5f79134b1202843ab568ba1e1b26be213830f08a89
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- checkoff (0.56.0)
15
+ checkoff (0.58.0)
16
16
  activesupport
17
17
  asana (> 0.10.0)
18
18
  cache_method
@@ -45,7 +45,7 @@ GEM
45
45
  concurrent-ruby (1.2.2)
46
46
  crack (0.4.5)
47
47
  rexml
48
- dalli (3.2.5)
48
+ dalli (3.2.6)
49
49
  diff-lcs (1.5.0)
50
50
  docile (1.4.0)
51
51
  e2mmap (0.1.0)
@@ -139,7 +139,7 @@
139
139
  # #
140
140
  # # @param per_page [Integer] the number of records to fetch per page.
141
141
  # # @param options [Hash] the request I/O options.
142
- # # @return [Asana::Collection<Asana::Resources::Project>]
142
+ # # @return [Enumerable<Asana::Resources::Project>]
143
143
  # def find_by_workspace(client, workspace: required("workspace"), is_template: nil, archived: nil, per_page: 20, options: {}); end
144
144
  # # Returns the complete project record for a single project.
145
145
  # #
@@ -37,7 +37,7 @@ module Checkoff
37
37
  #
38
38
  # @sg-ignore
39
39
  # @return [Date, nil]
40
- def pull_date_field_by_name_or_raise(task, field_name)
40
+ def pull_date_field_by_name(task, field_name)
41
41
  if field_name == :modified
42
42
  return Time.parse(task.modified_at).to_date unless task.modified_at.nil?
43
43
 
@@ -55,6 +55,31 @@ module Checkoff
55
55
  raise "Teach me how to handle field #{field_name}"
56
56
  end
57
57
 
58
+ # @param task [Asana::Resources::Task]
59
+ # @param field_name [Symbol]
60
+ #
61
+ # @sg-ignore
62
+ # @return [Date, Time, nil]
63
+ def pull_date_or_time_field_by_name(task, field_name)
64
+ if field_name == :due
65
+ return Time.parse(task.due_at) unless task.due_at.nil?
66
+
67
+ return Date.parse(task.due_on) unless task.due_on.nil?
68
+
69
+ return nil
70
+ end
71
+
72
+ if field_name == :start
73
+ return Time.parse(task.start_at) unless task.start_at.nil?
74
+
75
+ return Date.parse(task.start_on) unless task.start_on.nil?
76
+
77
+ return nil
78
+ end
79
+
80
+ raise "Teach me how to handle field #{field_name}"
81
+ end
82
+
58
83
  # @sg-ignore
59
84
  # @param task [Asana::Resources::Task]
60
85
  # @param custom_field_gid [String]
@@ -257,9 +282,10 @@ module Checkoff
257
282
  end
258
283
 
259
284
  # @param task [Asana::Resources::Task]
285
+ # @param ignore_dependencies [Boolean]
260
286
  # @return [Boolean]
261
- def evaluate(task)
262
- @tasks.task_ready?(task)
287
+ def evaluate(task, ignore_dependencies: false)
288
+ @tasks.task_ready?(task, ignore_dependencies: ignore_dependencies)
263
289
  end
264
290
  end
265
291
 
@@ -292,6 +318,53 @@ module Checkoff
292
318
  end
293
319
  end
294
320
 
321
+ # :due_between_n_days function
322
+ class DueBetweenRelativePFunctionEvaluator < FunctionEvaluator
323
+ FUNCTION_NAME = :due_between_relative
324
+
325
+ def matches?
326
+ fn?(task_selector, FUNCTION_NAME)
327
+ end
328
+
329
+ # @param _index [Integer]
330
+ def evaluate_arg?(_index)
331
+ false
332
+ end
333
+
334
+ # @param task [Asana::Resources::Task]
335
+ # @param beginning_num_days_from_now [Integer]
336
+ # @param end_num_days_from_now [Integer]
337
+ # @param ignore_dependencies [Boolean]
338
+ #
339
+ # @return [Boolean]
340
+ def evaluate(task, beginning_num_days_from_now, end_num_days_from_now, ignore_dependencies: false)
341
+ beginning_n_days_from_now_time = (Time.now + (beginning_num_days_from_now * 24 * 60 * 60))
342
+ end_n_days_from_now_time = (Time.now + (end_num_days_from_now * 24 * 60 * 60))
343
+
344
+ # @type [Date, Time, nil]
345
+ task_date_or_time = pull_date_or_time_field_by_name(task, :start) ||
346
+ pull_date_or_time_field_by_name(task, :due)
347
+
348
+ return false if task_date_or_time.nil?
349
+
350
+ # if time
351
+ in_range = if task_date_or_time.is_a?(Time)
352
+ task_date_or_time > beginning_n_days_from_now_time &&
353
+ task_date_or_time <= end_n_days_from_now_time
354
+ else
355
+ # if date
356
+ task_date_or_time > beginning_n_days_from_now_time.to_date &&
357
+ task_date_or_time <= end_n_days_from_now_time.to_date
358
+ end
359
+
360
+ return false unless in_range
361
+
362
+ return false if !ignore_dependencies && @tasks.incomplete_dependencies?(task)
363
+
364
+ true
365
+ end
366
+ end
367
+
295
368
  # :custom_field_value function
296
369
  class CustomFieldValueFunctionEvaluator < FunctionEvaluator
297
370
  FUNCTION_NAME = :custom_field_value
@@ -387,7 +460,7 @@ module Checkoff
387
460
  end
388
461
 
389
462
  # :field_less_than_n_days_ago
390
- class FieldLessThanNDaysAgoFunctionEvaluator < FunctionEvaluator
463
+ class FieldLessThanNDaysAgoPFunctionEvaluator < FunctionEvaluator
391
464
  FUNCTION_NAME = :field_less_than_n_days_ago
392
465
 
393
466
  def matches?
@@ -404,7 +477,7 @@ module Checkoff
404
477
  #
405
478
  # @return [Boolean]
406
479
  def evaluate(task, field_name, num_days)
407
- date = pull_date_field_by_name_or_raise(task, field_name)
480
+ date = pull_date_field_by_name(task, field_name)
408
481
 
409
482
  return false if date.nil?
410
483
 
@@ -416,7 +489,7 @@ module Checkoff
416
489
  end
417
490
 
418
491
  # :field_greater_than_or_equal_to_n_days_from_today
419
- class FieldGreaterThanOrEqualToNDaysFromTodayFunctionEvaluator < FunctionEvaluator
492
+ class FieldGreaterThanOrEqualToNDaysFromTodayPFunctionEvaluator < FunctionEvaluator
420
493
  FUNCTION_NAME = :field_greater_than_or_equal_to_n_days_from_today
421
494
 
422
495
  def matches?
@@ -433,7 +506,7 @@ module Checkoff
433
506
  #
434
507
  # @return [Boolean]
435
508
  def evaluate(task, field_name, num_days)
436
- date = pull_date_field_by_name_or_raise(task, field_name)
509
+ date = pull_date_field_by_name(task, field_name)
437
510
 
438
511
  return false if date.nil?
439
512
 
@@ -614,10 +687,11 @@ module Checkoff
614
687
  Checkoff::TaskSelectorClasses::AndFunctionEvaluator,
615
688
  Checkoff::TaskSelectorClasses::OrFunctionEvaluator,
616
689
  Checkoff::TaskSelectorClasses::DuePFunctionEvaluator,
690
+ Checkoff::TaskSelectorClasses::DueBetweenRelativePFunctionEvaluator,
617
691
  Checkoff::TaskSelectorClasses::UnassignedPFunctionEvaluator,
618
692
  Checkoff::TaskSelectorClasses::DueDateSetPFunctionEvaluator,
619
- Checkoff::TaskSelectorClasses::FieldLessThanNDaysAgoFunctionEvaluator,
620
- Checkoff::TaskSelectorClasses::FieldGreaterThanOrEqualToNDaysFromTodayFunctionEvaluator,
693
+ Checkoff::TaskSelectorClasses::FieldLessThanNDaysAgoPFunctionEvaluator,
694
+ Checkoff::TaskSelectorClasses::FieldGreaterThanOrEqualToNDaysFromTodayPFunctionEvaluator,
621
695
  Checkoff::TaskSelectorClasses::CustomFieldLessThanNDaysFromNowFunctionEvaluator,
622
696
  Checkoff::TaskSelectorClasses::CustomFieldGreaterThanOrEqualToNDaysFromNowFunctionEvaluator,
623
697
  Checkoff::TaskSelectorClasses::LastStoryCreatedLessThanNDaysAgoFunctionEvaluator,
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'forwardable'
6
+ require 'cache_method'
7
+ require_relative 'internal/config_loader'
8
+ require_relative 'workspaces'
9
+ require_relative 'clients'
10
+
11
+ # https://developers.asana.com/docs/project-selectors
12
+
13
+ module Checkoff
14
+ # Filter lists of projects using declarative selectors.
15
+ class ProjectSelectors
16
+ MINUTE = 60
17
+ HOUR = MINUTE * 60
18
+ DAY = 24 * HOUR
19
+ REALLY_LONG_CACHE_TIME = HOUR * 1
20
+ LONG_CACHE_TIME = MINUTE * 15
21
+ SHORT_CACHE_TIME = MINUTE
22
+
23
+ # @param config [Hash<Symbol, Object>]
24
+ # @param workspaces [Checkoff::Workspaces]
25
+ # @param clients [Checkoff::Clients]
26
+ # @param client [Asana::Client]
27
+ def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
28
+ workspaces: Checkoff::Workspaces.new(config: config),
29
+ clients: Checkoff::Clients.new(config: config),
30
+ client: clients.client)
31
+ @workspaces = workspaces
32
+ @client = client
33
+ end
34
+
35
+ # @param [Asana::Resources::Project] project
36
+ # @param [Array<(Symbol, Array)>] project_selector Filter based on
37
+ # project details. Examples: [:tag, 'foo'] [:not, [:tag, 'foo']] [:tag, 'foo']
38
+ # @return [Boolean]
39
+ def filter_via_project_selector(project, project_selector)
40
+ if project_selector == [:custom_field_values_contain_any_value, 'Project attributes', ['timeline']]
41
+ custom_field = project.custom_fields.find { |field| field.fetch('name') == 'Project attributes' }
42
+
43
+ return false if custom_field.nil?
44
+
45
+ # @sg-ignore
46
+ # @type [Hash, nil]
47
+ timeline = custom_field.fetch('multi_enum_values').find do |multi_enum_value|
48
+ multi_enum_value.fetch('name') == 'timeline'
49
+ end
50
+
51
+ return !timeline.nil?
52
+ end
53
+
54
+ raise "Teach me how to evaluate #{project} against #{project_selector}"
55
+ end
56
+
57
+ private
58
+
59
+ # @return [Checkoff::Workspaces]
60
+ attr_reader :workspaces
61
+
62
+ # @return [Asana::Client]
63
+ attr_reader :client
64
+
65
+ # bundle exec ./project_selectors.rb
66
+ # :nocov:
67
+ class << self
68
+ # @return [void]
69
+ def run
70
+ # workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
71
+ # project_selector_name = ARGV[1] || raise('Please pass project_selector name as second argument')
72
+ # project_selectors = Checkoff::ProjectSelectors.new
73
+ # project_selector = project_selectors.project_selector_or_raise(workspace_name, project_selector_name)
74
+ # puts "Results: #{project_selector}"
75
+ end
76
+ end
77
+ # :nocov:
78
+ end
79
+ end
80
+
81
+ # :nocov:
82
+ abs_program_name = File.expand_path($PROGRAM_NAME)
83
+ Checkoff::ProjectSelectors.run if abs_program_name == File.expand_path(__FILE__)
84
+ # :nocov:
@@ -59,7 +59,7 @@ module Checkoff
59
59
  if project_name.is_a?(Symbol) && project_name.to_s.start_with?('my_tasks')
60
60
  my_tasks(workspace_name)
61
61
  else
62
- # @type [Asana::Collection<Asana::Resources::Project>]
62
+ # @type [Enumerable<Asana::Resources::Project>]
63
63
  ps = projects_by_workspace_name(workspace_name)
64
64
  ps.find do |project|
65
65
  project.name == project_name
@@ -102,6 +102,17 @@ module Checkoff
102
102
  end
103
103
  cache_method :tasks_from_project, SHORT_CACHE_TIME
104
104
 
105
+ # @param [String] workspace_name
106
+ # @param [Array<String>] extra_fields
107
+ # @return [Enumerable<Asana::Resources::Project>]
108
+ def projects_by_workspace_name(workspace_name, extra_fields: [])
109
+ workspace = @workspaces.workspace_or_raise(workspace_name)
110
+ options = { fields: %w[name] + extra_fields }
111
+ projects.find_by_workspace(workspace: workspace.gid, per_page: 100, options: options)
112
+ end
113
+ # 15 minute cache resulted in 'Your pagination token has expired'
114
+ cache_method :projects_by_workspace_name, MEDIUM_CACHE_TIME
115
+
105
116
  private
106
117
 
107
118
  # @return [Asana::Client]
@@ -113,17 +124,6 @@ module Checkoff
113
124
  end
114
125
  cache_method :projects, LONG_CACHE_TIME
115
126
 
116
- # @param [String] workspace_name
117
- # @return [Asana::Collection<Asana::Resources::Project>]
118
- def projects_by_workspace_name(workspace_name)
119
- workspace = @workspaces.workspace_or_raise(workspace_name)
120
- raise "Could not find workspace named #{workspace_name}" unless workspace
121
-
122
- projects.find_by_workspace(workspace: workspace.gid, per_page: 100)
123
- end
124
- # 15 minute cache resulted in 'Your pagination token has expired'
125
- cache_method :projects_by_workspace_name, MEDIUM_CACHE_TIME
126
-
127
127
  # @param [String] workspace_name
128
128
  # @return [Asana::Resources::Project]
129
129
  def my_tasks(workspace_name)
@@ -204,7 +204,7 @@ module Checkoff
204
204
 
205
205
  project = projects.project(workspace_name, project_name)
206
206
  if project.nil?
207
- raise "Could not find project #{project_name} " \
207
+ raise "Could not find project #{project_name.inspect} " \
208
208
  "under workspace #{workspace_name}"
209
209
  end
210
210
  project
@@ -103,6 +103,29 @@ module Checkoff
103
103
  "https://app.asana.com/0/0/#{task.gid}/f"
104
104
  end
105
105
 
106
+ # @param task [Asana::Resources::Task]
107
+ def incomplete_dependencies?(task)
108
+ # Avoid a redundant fetch. Unfortunately, Ruby SDK allows
109
+ # dependencies to be fetched along with other attributes--but
110
+ # then doesn't use it and does another HTTP GET! At least this
111
+ # way we can skip the extra HTTP GET in the common case when
112
+ # there are no dependencies.
113
+ #
114
+ # https://github.com/Asana/ruby-asana/issues/125
115
+
116
+ # @sg-ignore
117
+ # @type [Enumerable<Asana::Resources::Task>, nil]
118
+ dependencies = task.instance_variable_get(:@dependencies)
119
+ dependencies = task.dependencies.map { |dependency| { 'gid' => dependency.gid } } if dependencies.nil?
120
+
121
+ dependencies.any? do |parent_task_info|
122
+ parent_task_gid = parent_task_info.fetch('gid')
123
+ parent_task = @asana_task.find_by_id(client, parent_task_gid,
124
+ options: { fields: ['completed'] })
125
+ !parent_task.completed
126
+ end
127
+ end
128
+
106
129
  private
107
130
 
108
131
  # @param workspace_name [String]
@@ -158,28 +181,5 @@ module Checkoff
158
181
 
159
182
  nil
160
183
  end
161
-
162
- # @param task [Asana::Resources::Task]
163
- def incomplete_dependencies?(task)
164
- # Avoid a redundant fetch. Unfortunately, Ruby SDK allows
165
- # dependencies to be fetched along with other attributes--but
166
- # then doesn't use it and does another HTTP GET! At least this
167
- # way we can skip the extra HTTP GET in the common case when
168
- # there are no dependencies.
169
- #
170
- # https://github.com/Asana/ruby-asana/issues/125
171
-
172
- # @sg-ignore
173
- # @type [Enumerable<Asana::Resources::Task>, nil]
174
- dependencies = task.instance_variable_get(:@dependencies)
175
- dependencies = task.dependencies.map { |task| { 'gid' => task.gid } } if dependencies.nil?
176
-
177
- dependencies.any? do |parent_task_info|
178
- parent_task_gid = parent_task_info.fetch('gid')
179
- parent_task = @asana_task.find_by_id(client, parent_task_gid,
180
- options: { fields: ['completed'] })
181
- !parent_task.completed
182
- end
183
- end
184
184
  end
185
185
  end
@@ -3,5 +3,5 @@
3
3
  # Command-line and gem client for Asana (unofficial)
4
4
  module Checkoff
5
5
  # Version of library
6
- VERSION = '0.56.0'
6
+ VERSION = '0.58.0'
7
7
  end
data/lib/checkoff.rb CHANGED
@@ -10,4 +10,5 @@ require 'checkoff/tasks'
10
10
  require 'checkoff/custom_fields'
11
11
  require 'checkoff/tags'
12
12
  require 'checkoff/task_selectors'
13
+ require 'checkoff/project_selectors'
13
14
  require 'checkoff/task_searches'
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.56.0
4
+ version: 0.58.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-09-17 00:00:00.000000000 Z
11
+ date: 2023-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -141,6 +141,7 @@ files:
141
141
  - lib/checkoff/internal/search_url/simple_param_converter.rb
142
142
  - lib/checkoff/internal/task_selector_evaluator.rb
143
143
  - lib/checkoff/my_tasks.rb
144
+ - lib/checkoff/project_selectors.rb
144
145
  - lib/checkoff/projects.rb
145
146
  - lib/checkoff/sections.rb
146
147
  - lib/checkoff/subtasks.rb