checkoff 0.18.0 → 0.19.1

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: 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