checkoff 0.18.0 → 0.19.1

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: ebf4493d6d8ae139dcc63fa7178ed04f1aa596b2b6b4c82d16f512daac2d19e0
4
- data.tar.gz: b6116733287bc50b94aaadd541b1d9789cd3a17f0ff82e0d2fc645a4c279070f
3
+ metadata.gz: b6a571f1d0bef00f4e030c22ea9a1cfae923c5253f8949deeaa24d537709c723
4
+ data.tar.gz: 1545211bc1d508f12fd26769d41201818ea280714c96c685ca31148950ba56f9
5
5
  SHA512:
6
- metadata.gz: 408c57bfb26c63a750c8eb38723eb27b4b33fdf283b99b8890f84d59515a0cea2274226059e4c4cd848e5cef5b73c99a1d9bdb78746210ec06aef0ff6dd60aaf
7
- data.tar.gz: 29e6d4ebfccf6b1bee268d0cf1e753f70402be79c63def06610e13a0b426ed9e534db230f32b13385831c473ae56cb21feb0d4812f3bfa2c70ab0b187c884111
6
+ metadata.gz: '08f83449b8628676c61c2afc08a9723b0639572e42c646e2fd81dc8571f446ac5a5e46ccbc58ce3ea2dddc5538ac7736081f5efde01bf46fc3b2111872f7fa93'
7
+ data.tar.gz: ddefd4b5d02bb05a610e0ead08c3126c76f9373ceb6a339ac61777a997de753b25491447df52c4127b96bf4fb8dac01f9e57183d82da3c61f121c629f0e1a757
data/.circleci/config.yml CHANGED
@@ -83,6 +83,7 @@ commands:
83
83
 
84
84
  jobs:
85
85
  overcommit:
86
+ working_directory: ~/checkoff
86
87
  docker:
87
88
  - image: apiology/circleci-ruby:latest
88
89
  steps:
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- checkoff (0.18.0)
15
+ checkoff (0.19.1)
16
16
  activesupport
17
17
  asana (> 0.10.0)
18
18
  cache_method
@@ -63,7 +63,7 @@ GEM
63
63
  faraday_middleware
64
64
  multi_json
65
65
  gli (2.21.0)
66
- i18n (1.11.0)
66
+ i18n (1.12.0)
67
67
  concurrent-ruby (~> 1.0)
68
68
  imagen (0.1.8)
69
69
  parser (>= 2.5, != 2.5.1.1)
@@ -138,7 +138,7 @@ GEM
138
138
  simplecov-lcov (0.8.0)
139
139
  simplecov_json_formatter (0.1.3)
140
140
  tomlrb (2.0.3)
141
- tzinfo (2.0.4)
141
+ tzinfo (2.0.5)
142
142
  concurrent-ruby (~> 1.0)
143
143
  undercover (0.4.3)
144
144
  imagen (>= 0.1.8)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "result": {
3
- "line": 98.68,
4
- "branch": 93.26
3
+ "line": 99.61,
4
+ "branch": 86.52
5
5
  }
6
6
  }
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module Checkoff
6
+ module Internal
7
+ # base class for handling different custom_field_#{gid}.variant params
8
+ class CustomFieldVariant
9
+ def initialize(gid, remaining_params)
10
+ @gid = gid
11
+ @remaining_params = remaining_params
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :gid, :remaining_params
17
+ end
18
+
19
+ # custom_field_#{gid}.variant = 'less_than'
20
+ class LessThanCustomFieldVariant < CustomFieldVariant
21
+ def convert
22
+ max_param = "custom_field_#{gid}.max"
23
+ case remaining_params.keys
24
+ when [max_param]
25
+ convert_single_custom_field_less_than_params_max_param(max_param)
26
+ else
27
+ raise "Teach me how to handle #{remaining_params}"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def convert_single_custom_field_less_than_params_max_param(max_param)
34
+ max_values = remaining_params.fetch(max_param)
35
+ unless max_values.length == 1
36
+ raise "Teach me how to handle these remaining keys for #{max_param}: #{remaining_params}"
37
+ end
38
+
39
+ max_value = max_values[0]
40
+ empty_task_selector = []
41
+ [{ "custom_fields.#{gid}.less_than" => max_value }, empty_task_selector]
42
+ end
43
+ end
44
+
45
+ # custom_field_#{gid}.variant = 'is_not'
46
+ class IsNotCustomFieldVariant < CustomFieldVariant
47
+ def convert
48
+ case remaining_params.keys
49
+ when ["custom_field_#{gid}.selected_options"]
50
+ convert_single_custom_field_is_not_params_selected_options
51
+ else
52
+ raise "Teach me how to handle #{remaining_params}"
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def convert_single_custom_field_is_not_params_selected_options
59
+ selected_options = remaining_params.fetch("custom_field_#{gid}.selected_options")
60
+ raise "Teach me how to handle #{remaining_params}" unless selected_options.length == 1
61
+
62
+ [{ "custom_fields.#{gid}.is_set" => 'true' },
63
+ ['not',
64
+ ['custom_field_gid_value_contains_any_gid',
65
+ gid,
66
+ selected_options.fetch(0).split('~')]]]
67
+ end
68
+ end
69
+
70
+ # custom_field_#{gid}.variant = 'no_value'
71
+ class NoValueCustomFieldVariant < CustomFieldVariant
72
+ def convert
73
+ unless remaining_params.length.zero?
74
+ raise "Teach me how to handle these remaining keys for #{variant_key}: #{remaining_params}"
75
+ end
76
+
77
+ empty_task_selector = []
78
+ [{ "custom_fields.#{gid}.is_set" => 'false' }, empty_task_selector]
79
+ end
80
+ end
81
+
82
+ # custom_field_#{gid}.variant = 'is'
83
+ class IsCustomFieldVariant < CustomFieldVariant
84
+ def convert
85
+ unless remaining_params.length == 1
86
+ raise "Teach me how to handle these remaining keys for #{variant_key}: #{remaining_params}"
87
+ end
88
+
89
+ key, values = remaining_params.to_a[0]
90
+ convert_custom_field_is_arg(key, values)
91
+ end
92
+
93
+ private
94
+
95
+ def convert_custom_field_is_arg(key, values)
96
+ empty_task_selector = []
97
+
98
+ if key.end_with? '.selected_options'
99
+ raise "Too many values found for #{key}: #{values}" if values.length != 1
100
+
101
+ return [{ "custom_fields.#{gid}.value" => values[0] },
102
+ empty_task_selector]
103
+ end
104
+
105
+ raise "Teach me how to handle #{key} = #{values}"
106
+ end
107
+ end
108
+
109
+ # Convert custom field parameters from an Asana search URL into
110
+ # API search arguments and Checkoff task selectors
111
+ class CustomFieldParamConverter
112
+ def initialize(custom_field_params:)
113
+ @custom_field_params = custom_field_params
114
+ end
115
+
116
+ def convert
117
+ args = {}
118
+ task_selector = []
119
+ by_custom_field.each do |gid, single_custom_field_params|
120
+ new_args, new_task_selector = convert_single_custom_field_params(gid,
121
+ single_custom_field_params)
122
+ args, task_selector = merge_args_and_task_selectors(args, new_args,
123
+ task_selector, new_task_selector)
124
+ end
125
+ [args, task_selector]
126
+ end
127
+
128
+ private
129
+
130
+ def by_custom_field
131
+ custom_field_params.group_by do |key, _value|
132
+ gid_from_custom_field_key(key)
133
+ end.transform_values(&:to_h)
134
+ end
135
+
136
+ def merge_args_and_task_selectors(args, new_args, task_selector, new_task_selector)
137
+ args = args.merge(new_args)
138
+ return [args, task_selector] if new_task_selector == []
139
+
140
+ raise 'Teach me how to merge task selectors' unless task_selector == []
141
+
142
+ task_selector = new_task_selector
143
+
144
+ [args, task_selector]
145
+ end
146
+
147
+ VARIANTS = {
148
+ 'is' => IsCustomFieldVariant,
149
+ 'no_value' => NoValueCustomFieldVariant,
150
+ 'is_not' => IsNotCustomFieldVariant,
151
+ 'less_than' => LessThanCustomFieldVariant,
152
+ }.freeze
153
+
154
+ def convert_single_custom_field_params(gid, single_custom_field_params)
155
+ variant_key = "custom_field_#{gid}.variant"
156
+ variant = single_custom_field_params.fetch(variant_key)
157
+ remaining_params = single_custom_field_params.reject { |k, _v| k == variant_key }
158
+ raise "Teach me how to handle #{variant_key} = #{variant}" unless variant.length == 1
159
+
160
+ variant_class = VARIANTS[variant[0]]
161
+ return variant_class.new(gid, remaining_params).convert unless variant_class.nil?
162
+
163
+ raise "Teach me how to handle #{variant_key} = #{variant}"
164
+ end
165
+
166
+ def gid_from_custom_field_key(key)
167
+ key.split('_')[2].split('.')[0]
168
+ end
169
+
170
+ attr_reader :custom_field_params
171
+ end
172
+
173
+ # Parse Asana search URLs into parameters suitable to pass into
174
+ # the /workspaces/{workspace_gid}/tasks/search endpoint
175
+ class SearchUrlParser
176
+ def initialize(_deps = {}); end
177
+
178
+ def convert_params(url)
179
+ url_params = CGI.parse(URI.parse(url).query)
180
+ custom_field_params, regular_url_params = partition_url_params(url_params)
181
+ custom_field_args, task_selector = convert_custom_field_params(custom_field_params)
182
+ regular_url_args = convert_regular_params(regular_url_params)
183
+ [custom_field_args.merge(regular_url_args), task_selector]
184
+ end
185
+
186
+ private
187
+
188
+ def convert_regular_params(regular_url_params)
189
+ regular_url_params.to_a.map do |key, values|
190
+ convert_arg(key, values)
191
+ end.to_h
192
+ end
193
+
194
+ def convert_custom_field_params(custom_field_params)
195
+ CustomFieldParamConverter.new(custom_field_params: custom_field_params).convert
196
+ end
197
+
198
+ def partition_url_params(url_params)
199
+ url_params.to_a.partition do |key, _values|
200
+ key.start_with? 'custom_field_'
201
+ end.map(&:to_h)
202
+ end
203
+
204
+ # https://developers.asana.com/docs/search-tasks-in-a-workspace
205
+ def convert_arg(key, values)
206
+ case key
207
+ when 'any_projects.ids'
208
+ ['projects.any', values.join(',')]
209
+ when 'completion'
210
+ raise "Teach me how to handle #{key} = #{values}" if values.length != 1
211
+
212
+ value = values.fetch(0)
213
+ raise "Teach me how to handle #{key} = #{values}" if value != 'incomplete'
214
+
215
+ ['completed', false]
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkoff
4
+ # Base class to evaluate a task selector function given fully evaluated arguments
5
+ class FunctionEvaluator
6
+ def initialize(task_selector:)
7
+ @task_selector = task_selector
8
+ end
9
+
10
+ def evaluate_arg?(_index)
11
+ true
12
+ end
13
+
14
+ private
15
+
16
+ def fn?(object, fn_name)
17
+ object.is_a?(Array) && !object.empty? && [fn_name, fn_name.to_s].include?(object[0])
18
+ end
19
+
20
+ attr_reader :task_selector
21
+ end
22
+
23
+ # :not function
24
+ class NotFunctionEvaluator < FunctionEvaluator
25
+ def matches?
26
+ fn?(task_selector, :not)
27
+ end
28
+
29
+ def evaluate(_task, subvalue)
30
+ !subvalue
31
+ end
32
+ end
33
+
34
+ # :nil? function
35
+ class NilPFunctionEvaluator < FunctionEvaluator
36
+ def matches?
37
+ fn?(task_selector, :nil?)
38
+ end
39
+
40
+ def evaluate(_task, subvalue)
41
+ subvalue.nil?
42
+ end
43
+ end
44
+
45
+ # :tag function
46
+ class TagPFunctionEvaluator < FunctionEvaluator
47
+ def matches?
48
+ fn?(task_selector, :tag)
49
+ end
50
+
51
+ def evaluate_arg?(_index)
52
+ false
53
+ end
54
+
55
+ def evaluate(task, tag_name)
56
+ task.tags.map(&:name).include? tag_name
57
+ end
58
+ end
59
+
60
+ # :custom_field_value function
61
+ class CustomFieldValueFunctionEvaluator < FunctionEvaluator
62
+ def matches?
63
+ fn?(task_selector, :custom_field_value)
64
+ end
65
+
66
+ def evaluate_arg?(_index)
67
+ false
68
+ end
69
+
70
+ def evaluate(task, custom_field_name)
71
+ custom_fields = task.custom_fields
72
+ if custom_fields.nil?
73
+ raise "custom fields not found on task - did you add 'custom_field' in your extra_fields argument?"
74
+ end
75
+
76
+ custom_field = custom_fields.find { |field| field.fetch('name') == custom_field_name }
77
+ return nil if custom_field.nil?
78
+
79
+ custom_field['display_value']
80
+ end
81
+ end
82
+
83
+ # :custom_field_gid_value_contains_any_gid function
84
+ class CustomFieldGidValueContainsAnyGidFunctionEvaluator < FunctionEvaluator
85
+ def matches?
86
+ fn?(task_selector, :custom_field_gid_value_contains_any_gid)
87
+ end
88
+
89
+ def evaluate_arg?(_index)
90
+ false
91
+ end
92
+
93
+ def evaluate(task, custom_field_gid, custom_field_values_gids)
94
+ actual_custom_field_values_gids = pull_custom_field_values_gids(task, custom_field_gid)
95
+
96
+ (custom_field_values_gids - actual_custom_field_values_gids).empty?
97
+ end
98
+
99
+ private
100
+
101
+ def pull_custom_field_or_raise(task, custom_field_gid)
102
+ custom_fields = task.custom_fields
103
+ if custom_fields.nil?
104
+ raise "Could not find custom_fields under task (was 'custom_fields' included in 'extra_fields'?)"
105
+ end
106
+
107
+ matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid }
108
+ raise "Could not find custom field with gid #{custom_field_gid}" if matched_custom_field.nil?
109
+
110
+ matched_custom_field
111
+ end
112
+
113
+ def pull_custom_field_values_gids(task, custom_field_gid)
114
+ matched_custom_field = pull_custom_field_or_raise(task, custom_field_gid)
115
+
116
+ enum_value = matched_custom_field.fetch('enum_value')
117
+ actual_custom_field_values_gids = []
118
+ unless enum_value.nil?
119
+ if enum_value.fetch('enabled') == false
120
+ raise "Unexpected enabled value on custom field: #{matched_custom_field}"
121
+ end
122
+
123
+ actual_custom_field_values_gids = [enum_value.fetch('gid')]
124
+ end
125
+ actual_custom_field_values_gids
126
+ end
127
+ end
128
+
129
+ # Evaluator task selectors against a task
130
+ class TaskSelectorEvaluator
131
+ def initialize(task:)
132
+ @task = task
133
+ end
134
+
135
+ FUNCTION_EVALUTORS = [
136
+ NotFunctionEvaluator,
137
+ NilPFunctionEvaluator,
138
+ TagPFunctionEvaluator,
139
+ CustomFieldValueFunctionEvaluator,
140
+ CustomFieldGidValueContainsAnyGidFunctionEvaluator,
141
+ ].freeze
142
+
143
+ def evaluate(task_selector)
144
+ return true if task_selector == []
145
+
146
+ FUNCTION_EVALUTORS.each do |evaluator_class|
147
+ evaluator = evaluator_class.new(task_selector: task_selector)
148
+
149
+ next unless evaluator.matches?
150
+
151
+ return try_this_evaluator(task_selector, evaluator)
152
+ end
153
+
154
+ raise "Syntax issue trying to handle #{task_selector}"
155
+ end
156
+
157
+ private
158
+
159
+ def try_this_evaluator(task_selector, evaluator)
160
+ evaluated_args = task_selector[1..].map.with_index do |item, index|
161
+ if evaluator.evaluate_arg?(index)
162
+ evaluate(item)
163
+ else
164
+ item
165
+ end
166
+ end
167
+
168
+ evaluator.evaluate(task, *evaluated_args)
169
+ end
170
+
171
+ attr_reader :task, :task_selector
172
+ end
173
+ end
@@ -0,0 +1,89 @@
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 'projects'
10
+ require_relative 'clients'
11
+ require_relative 'task_selectors'
12
+ require 'asana/resource_includes/collection'
13
+ require 'asana/resource_includes/response_helper'
14
+
15
+ require 'checkoff/internal/search_url_parser'
16
+
17
+ # https://developers.asana.com/docs/task-searches
18
+ module Checkoff
19
+ # Run task searches against the Asana API
20
+ class TaskSearches
21
+ MINUTE = 60
22
+ HOUR = MINUTE * 60
23
+ DAY = 24 * HOUR
24
+ REALLY_LONG_CACHE_TIME = HOUR * 1
25
+ LONG_CACHE_TIME = MINUTE * 15
26
+ SHORT_CACHE_TIME = MINUTE
27
+
28
+ include Asana::Resources::ResponseHelper
29
+
30
+ def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
31
+ workspaces: Checkoff::Workspaces.new(config: config),
32
+ task_selectors: Checkoff::TaskSelectors.new(config: config),
33
+ projects: Checkoff::Projects.new(config: config),
34
+ clients: Checkoff::Clients.new(config: config),
35
+ client: clients.client,
36
+ search_url_parser: Checkoff::Internal::SearchUrlParser.new,
37
+ asana_resources_collection_class: Asana::Resources::Collection)
38
+ @workspaces = workspaces
39
+ @task_selectors = task_selectors
40
+ @projects = projects
41
+ @client = client
42
+ @search_url_parser = search_url_parser
43
+ @asana_resources_collection_class = asana_resources_collection_class
44
+ end
45
+
46
+ def task_search(workspace_name, url, extra_fields: [])
47
+ workspace = workspaces.workspace_or_raise(workspace_name)
48
+ api_params, task_selector = @search_url_parser.convert_params(url)
49
+ path = "/workspaces/#{workspace.gid}/tasks/search"
50
+ options = calculate_api_options(extra_fields)
51
+ tasks = @asana_resources_collection_class.new(parse(client.get(path,
52
+ params: api_params,
53
+ options: options)),
54
+ type: Asana::Resources::Task,
55
+ client: client)
56
+ tasks.select { |task| task_selectors.filter_via_task_selector(task, task_selector) }
57
+ end
58
+ cache_method :task_search, LONG_CACHE_TIME
59
+
60
+ private
61
+
62
+ def calculate_api_options(extra_fields)
63
+ options = projects.task_options[:options]
64
+ options[:fields] += ['custom_fields']
65
+ options[:fields] += extra_fields
66
+ options
67
+ end
68
+
69
+ # bundle exec ./task_searches.rb
70
+ # :nocov:
71
+ class << self
72
+ def run
73
+ workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
74
+ url = ARGV[1] || raise('Please pass task search URL as second argument')
75
+ task_searches = Checkoff::TaskSearches.new
76
+ task_search = task_searches.task_search(workspace_name, url)
77
+ puts "Results: #{task_search}"
78
+ end
79
+ end
80
+ # :nocov:
81
+
82
+ attr_reader :task_selectors, :projects, :workspaces, :client
83
+ end
84
+ end
85
+
86
+ # :nocov:
87
+ abs_program_name = File.expand_path($PROGRAM_NAME)
88
+ Checkoff::TaskSearches.run if abs_program_name == __FILE__
89
+ # :nocov:
@@ -6,8 +6,7 @@ require 'forwardable'
6
6
  require 'cache_method'
7
7
  require 'json'
8
8
  require_relative 'internal/config_loader'
9
- require_relative 'workspaces'
10
- require_relative 'clients'
9
+ require_relative 'internal/task_selector_evaluator'
11
10
 
12
11
  # https://developers.asana.com/docs/task-selectors
13
12
 
@@ -21,49 +20,17 @@ module Checkoff
21
20
  LONG_CACHE_TIME = MINUTE * 15
22
21
  SHORT_CACHE_TIME = MINUTE
23
22
 
24
- def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
25
- workspaces: Checkoff::Workspaces.new(config: config),
26
- clients: Checkoff::Clients.new(config: config),
27
- client: clients.client)
28
- @workspaces = workspaces
29
- @client = client
23
+ def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana))
24
+ @config = config
30
25
  end
31
26
 
32
27
  # @param [Hash<Symbol, Object>] task_selector Filter based on
33
28
  # description. Examples: {tag: 'foo'} {:not {tag: 'foo'} (:tag 'foo')
34
29
  def filter_via_task_selector(task, task_selector)
35
- return true if task_selector == []
36
-
37
- return !filter_via_task_selector(task, task_selector.fetch(1)) if fn?(task_selector, :not)
38
-
39
- return filter_via_task_selector(task, task_selector.fetch(1)).nil? if fn?(task_selector, :nil?)
40
-
41
- return contains_tag?(task, task_selector.fetch(1)) if fn?(task_selector, :tag)
42
-
43
- return custom_field_value(task, task_selector.fetch(1)) if fn?(task_selector, :custom_field_value)
44
-
45
- raise "Syntax issue trying to handle #{task_selector}"
46
- end
47
-
48
- private
49
-
50
- def contains_tag?(task, tag_name)
51
- task.tags.map(&:name).include? tag_name
52
- end
53
-
54
- def custom_field_value(task, custom_field_name)
55
- custom_field = task.custom_fields.find { |field| field.fetch('name') == custom_field_name }
56
- return nil if custom_field.nil?
57
-
58
- custom_field['display_value']
30
+ evaluator = TaskSelectorEvaluator.new(task: task)
31
+ evaluator.evaluate(task_selector)
59
32
  end
60
33
 
61
- def fn?(object, fn_name)
62
- object.is_a?(Array) && !object.empty? && [fn_name, fn_name.to_s].include?(object[0])
63
- end
64
-
65
- attr_reader :workspaces, :client
66
-
67
34
  # bundle exec ./task_selectors.rb
68
35
  # :nocov:
69
36
  class << self
@@ -84,12 +51,13 @@ module Checkoff
84
51
  require 'checkoff/projects'
85
52
 
86
53
  task_selectors = Checkoff::TaskSelectors.new
87
- extra_fields = []
54
+ extra_fields = ['custom_fields']
88
55
  projects = Checkoff::Projects.new
89
- project = projects.project(workspace_name, project_name)
56
+ project = projects.project_or_raise(workspace_name, project_name)
90
57
  raw_tasks = projects.tasks_from_project(project, extra_fields: extra_fields)
91
58
  tasks = raw_tasks.filter { |task| task_selectors.filter_via_task_selector(task, task_selector) }
92
- puts "Results: #{tasks}"
59
+ # avoid n+1 queries generating the full task formatting
60
+ puts JSON.pretty_generate(tasks.map(&:to_h))
93
61
  end
94
62
  end
95
63
  # :nocov:
@@ -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.18.0'
6
+ VERSION = '0.19.1'
7
7
  end
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.18.0
4
+ version: 0.19.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vince Broz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-12 00:00:00.000000000 Z
11
+ date: 2022-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -322,11 +322,14 @@ files:
322
322
  - lib/checkoff/custom_fields.rb
323
323
  - lib/checkoff/internal/config_loader.rb
324
324
  - lib/checkoff/internal/create-class.sh
325
+ - lib/checkoff/internal/search_url_parser.rb
326
+ - lib/checkoff/internal/task_selector_evaluator.rb
325
327
  - lib/checkoff/my_tasks.rb
326
328
  - lib/checkoff/projects.rb
327
329
  - lib/checkoff/sections.rb
328
330
  - lib/checkoff/subtasks.rb
329
331
  - lib/checkoff/tags.rb
332
+ - lib/checkoff/task_searches.rb
330
333
  - lib/checkoff/task_selectors.rb
331
334
  - lib/checkoff/tasks.rb
332
335
  - lib/checkoff/version.rb