checkoff 0.19.2 → 0.20.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: 3b199a642ccf75e4369caeffa5fc9cd5248164dc76f23674af38ce77dc2dfb3a
4
- data.tar.gz: 70a1db49e52e64294d44b9e1b386464b5e9a9c8cf28497fab4493abc77932954
3
+ metadata.gz: ca8acc9a67554659cd34a8a9a60f539e834088bc896ef6e3bb7761e75183ac8a
4
+ data.tar.gz: ff10088e95968a73ccb5590b316a4b6e3f9ff49572ab87271f710bc9a02f25b3
5
5
  SHA512:
6
- metadata.gz: '0088414917271dba3b10842d25cfe151188325582fd2fd07e7832f0ab849ccb6058c342d7b3c8fc754f54ce52f639db791562f549d509b2ecd5701b2dfd52e41'
7
- data.tar.gz: b91b8209aa5b765723a742cf86e0490935667bfa06a0b16fd5a7269dd0a743d84608da35b2a8510951db4180a50ad252d4ea39c2130a98013d6e8bf160db4f93
6
+ metadata.gz: a80da7607358f742d06fec48be66df5b53d41832ecbc16bdaae3a023c9a929d3cfff0a983ccbbdb381d1079187589c0e3c367ade5b8f7b92fe36fcd5025bfc65
7
+ data.tar.gz: dc9126b9a3366626c81f1eef2f32c517f9b978e5ec07052cc2194bf718e2e9c778da2fe2b09b6a1d07213437b7bf3265d636dcd071bb7ca01d822819883d1087
data/.circleci/config.yml CHANGED
@@ -37,9 +37,9 @@ commands:
37
37
  sed -E -e 's/checkoff \([[:digit:]]+.[[:digit:]]+.[[:digit:]]+\)/checkoff (0.1.0)/g' \
38
38
  Gemfile.lock > Gemfile.lock.deversioned
39
39
  - restore_cache:
40
- key: gems-v1-{{ checksum "Gemfile.lock.deversioned" }}
40
+ key: gems-v2-{{ checksum "Gemfile.lock.deversioned" }}
41
41
  - restore_cache:
42
- key: wheels-v1-{{ checksum "requirements_dev.txt" }}
42
+ key: wheels-v2-{{ checksum "requirements_dev.txt" }}
43
43
  - run:
44
44
  name: Initialize packages
45
45
  command: |
@@ -54,11 +54,11 @@ commands:
54
54
  exit 1
55
55
  fi
56
56
  - save_cache:
57
- key: gems-v1-{{ checksum "Gemfile.lock.deversioned" }}
57
+ key: gems-v2-{{ checksum "Gemfile.lock.deversioned" }}
58
58
  paths:
59
59
  - "vendor/bundle"
60
60
  - save_cache:
61
- key: wheels-v1-{{ checksum "requirements_dev.txt" }}
61
+ key: wheels-v2-{{ checksum "requirements_dev.txt" }}
62
62
  paths:
63
63
  - "/home/circleci/.cache/pip/wheels"
64
64
  - run:
@@ -104,13 +104,8 @@ jobs:
104
104
  steps:
105
105
  - set_up_environment
106
106
  - run_with_languages:
107
- label: Test
108
- command: |
109
- bundle exec rake citest
110
- # https://github.com/bluelabsio/records-mover/blob/master/Makefile#L25
111
- git status --porcelain coverage/.last_run.json
112
- git diff coverage/.last_run.json
113
- test -z "$(git status --porcelain coverage/.last_run.json)"
107
+ label: test
108
+ command: make citest cicoverage
114
109
  # This seemed to shave 5ish% of the build time off when added
115
110
  resource_class: large
116
111
 
data/.overcommit.yml CHANGED
@@ -17,6 +17,15 @@
17
17
  # Uncomment the following lines to make the configuration take effect.
18
18
 
19
19
  PreCommit:
20
+ # Extend default config at https://github.com/sds/overcommit/blob/master/config/default.yml
21
+ BrokenSymlinks:
22
+ enabled: true
23
+ description: 'Check for broken symlinks'
24
+ quiet: true
25
+ exclude:
26
+ # This is a symlink to sibling checkout of vincelifedaily, used
27
+ # only in local development
28
+ - config/env.1p
20
29
  RuboCop:
21
30
  enabled: true
22
31
  on_warn: fail # Treat all warnings as failures
@@ -29,6 +38,10 @@ PreCommit:
29
38
  - '**/Gemfile'
30
39
  - '**/Rakefile'
31
40
  - 'bin/*'
41
+ - 'script/*'
42
+ exclude:
43
+ - db/migrate/*.rb
44
+ - db/schema.rb
32
45
  PythonFlake8:
33
46
  enabled: true
34
47
  on_warn: fail
@@ -37,6 +50,7 @@ PreCommit:
37
50
  on_warn: fail
38
51
  include:
39
52
  - '.envrc'
53
+ - '**/*.sh'
40
54
  YamlLint:
41
55
  enabled: true
42
56
  on_warn: fail
@@ -53,3 +67,10 @@ PrePush:
53
67
  include:
54
68
  - 'test/unit/**/test*.rb'
55
69
  enabled: true
70
+
71
+ #PostCheckout:
72
+ # ALL: # Special hook name that customizes all hooks of this type
73
+ # quiet: true # Change all post-checkout hooks to only display output on failure
74
+ #
75
+ # IndexTags:
76
+ # enabled: true # Generate a tags file with `ctags` each time HEAD changes
data/.rubocop.yml CHANGED
@@ -98,7 +98,7 @@ Style/TrivialAccessors:
98
98
 
99
99
  AllCops:
100
100
  NewCops: enable
101
- TargetRubyVersion: 2.6
101
+ TargetRubyVersion: 2.7
102
102
  Exclude:
103
103
  - 'bin/*'
104
104
  - 'vendor/**/*'
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- checkoff (0.19.2)
15
+ checkoff (0.20.0)
16
16
  activesupport
17
17
  asana (> 0.10.0)
18
18
  cache_method
@@ -22,13 +22,14 @@ PATH
22
22
  GEM
23
23
  remote: https://rubygems.org/
24
24
  specs:
25
- activesupport (6.1.6.1)
25
+ activesupport (7.0.4)
26
26
  concurrent-ruby (~> 1.0, >= 1.0.2)
27
27
  i18n (>= 1.6, < 2)
28
28
  minitest (>= 5.1)
29
29
  tzinfo (~> 2.0)
30
- zeitwerk (~> 2.3)
30
+ ansi (1.5.0)
31
31
  ast (2.4.2)
32
+ builder (3.2.4)
32
33
  bump (0.10.0)
33
34
  cache (0.4.1)
34
35
  cache_method (0.2.7)
@@ -39,7 +40,9 @@ GEM
39
40
  coderay (1.1.3)
40
41
  concurrent-ruby (1.1.10)
41
42
  dalli (3.2.2)
43
+ diff-lcs (1.5.0)
42
44
  docile (1.4.0)
45
+ fakeweb (1.3.0)
43
46
  faraday (1.5.1)
44
47
  faraday-em_http (~> 1.0)
45
48
  faraday-em_synchrony (~> 1.0)
@@ -68,12 +71,13 @@ GEM
68
71
  imagen (0.1.8)
69
72
  parser (>= 2.5, != 2.5.1.1)
70
73
  iniparse (1.5.0)
74
+ json (2.6.2)
71
75
  jwt (2.2.3)
72
76
  kramdown (2.4.0)
73
77
  rexml
74
78
  kramdown-parser-gfm (1.1.0)
75
79
  kramdown (~> 2.0)
76
- mdl (0.11.0)
80
+ mdl (0.12.0)
77
81
  kramdown (~> 2.3)
78
82
  kramdown-parser-gfm (~> 1.1)
79
83
  mixlib-cli (~> 2.1, >= 2.1.1)
@@ -82,12 +86,17 @@ GEM
82
86
  method_source (1.0.0)
83
87
  minitest (5.16.2)
84
88
  minitest-profile (0.0.2)
89
+ minitest-reporters (1.5.0)
90
+ ansi
91
+ builder
92
+ minitest (>= 5.0)
93
+ ruby-progressbar
85
94
  mixlib-cli (2.1.8)
86
95
  mixlib-config (3.0.27)
87
96
  tomlrb
88
97
  mixlib-shellout (3.2.7)
89
98
  chef-utils
90
- mocha (1.12.0)
99
+ mocha (2.0.0.alpha.1)
91
100
  multi_json (1.15.0)
92
101
  multi_xml (0.6.0)
93
102
  multipart-post (2.1.1)
@@ -101,35 +110,49 @@ GEM
101
110
  childprocess (>= 0.6.3, < 5)
102
111
  iniparse (~> 1.4)
103
112
  rexml (~> 3.2)
104
- parallel (1.20.1)
105
- parser (3.0.2.0)
113
+ parallel (1.22.1)
114
+ parser (3.1.2.1)
106
115
  ast (~> 2.4.1)
107
116
  pry (0.14.1)
108
117
  coderay (~> 1.1)
109
118
  method_source (~> 1.0)
110
119
  rack (2.2.4)
111
- rainbow (3.0.0)
120
+ rainbow (3.1.1)
112
121
  rake (13.0.3)
113
- regexp_parser (2.1.1)
122
+ regexp_parser (2.6.0)
114
123
  rexml (3.2.5)
115
- rubocop (1.18.4)
124
+ rspec (3.11.0)
125
+ rspec-core (~> 3.11.0)
126
+ rspec-expectations (~> 3.11.0)
127
+ rspec-mocks (~> 3.11.0)
128
+ rspec-core (3.11.0)
129
+ rspec-support (~> 3.11.0)
130
+ rspec-expectations (3.11.1)
131
+ diff-lcs (>= 1.2.0, < 2.0)
132
+ rspec-support (~> 3.11.0)
133
+ rspec-mocks (3.11.1)
134
+ diff-lcs (>= 1.2.0, < 2.0)
135
+ rspec-support (~> 3.11.0)
136
+ rspec-support (3.11.1)
137
+ rubocop (1.36.0)
138
+ json (~> 2.3)
116
139
  parallel (~> 1.10)
117
- parser (>= 3.0.0.0)
140
+ parser (>= 3.1.2.1)
118
141
  rainbow (>= 2.2.2, < 4.0)
119
142
  regexp_parser (>= 1.8, < 3.0)
120
- rexml
121
- rubocop-ast (>= 1.8.0, < 2.0)
143
+ rexml (>= 3.2.5, < 4.0)
144
+ rubocop-ast (>= 1.20.1, < 2.0)
122
145
  ruby-progressbar (~> 1.7)
123
146
  unicode-display_width (>= 1.4.0, < 3.0)
124
- rubocop-ast (1.8.0)
125
- parser (>= 3.0.1.1)
147
+ rubocop-ast (1.22.0)
148
+ parser (>= 3.1.1.0)
126
149
  rubocop-minitest (0.12.1)
127
150
  rubocop (>= 0.90, < 2.0)
128
151
  rubocop-rake (0.5.1)
129
152
  rubocop
130
153
  ruby-progressbar (1.11.0)
131
154
  ruby2_keywords (0.0.5)
132
- rugged (1.1.0)
155
+ rugged (1.5.0.1)
133
156
  simplecov (0.21.2)
134
157
  docile (~> 1.1)
135
158
  simplecov-html (~> 0.11)
@@ -140,12 +163,11 @@ GEM
140
163
  tomlrb (2.0.3)
141
164
  tzinfo (2.0.5)
142
165
  concurrent-ruby (~> 1.0)
143
- undercover (0.4.3)
166
+ undercover (0.4.5)
144
167
  imagen (>= 0.1.8)
145
168
  rainbow (>= 2.1, < 4.0)
146
- rugged (>= 0.27, < 1.2)
147
- unicode-display_width (2.0.0)
148
- zeitwerk (2.6.0)
169
+ rugged (>= 0.27, < 1.6)
170
+ unicode-display_width (2.3.0)
149
171
 
150
172
  PLATFORMS
151
173
  ruby
@@ -158,13 +180,16 @@ DEPENDENCIES
158
180
  bump
159
181
  bundler
160
182
  checkoff!
183
+ fakeweb
161
184
  mdl
162
185
  minitest-profile
163
- mocha
186
+ minitest-reporters
187
+ mocha (~> 2.0.0.alpha.1)
164
188
  overcommit (>= 0.58.0)
165
189
  pry
166
190
  rake (~> 13.0)
167
- rubocop
191
+ rspec (>= 3.4)
192
+ rubocop (~> 1.36)
168
193
  rubocop-minitest
169
194
  rubocop-rake
170
195
  simplecov (>= 0.18.0)
@@ -172,4 +197,4 @@ DEPENDENCIES
172
197
  undercover
173
198
 
174
199
  BUNDLED WITH
175
- 2.2.25
200
+ 2.3.23
data/Makefile CHANGED
@@ -15,7 +15,7 @@ export PRINT_HELP_PYSCRIPT
15
15
  help:
16
16
  @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
17
17
 
18
- default: localtest ## run default tests and quality
18
+ default: clean-coverage test coverage quality ## run default typechecking, tests and quality
19
19
 
20
20
  requirements_dev.txt.installed: requirements_dev.txt
21
21
  pip install -q --disable-pip-version-check -r requirements_dev.txt
@@ -40,6 +40,8 @@ clear_metrics: ## remove or reset result artifacts created by tests and quality
40
40
  clean: clear_metrics ## remove all built artifacts
41
41
 
42
42
 
43
+ citest: test ## Run unit tests from CircleCI
44
+
43
45
  typecheck: ## validate types in code and configuration
44
46
 
45
47
  overcommit: ## run precommit quality checks
@@ -56,6 +58,26 @@ localtest: ## run default local actions
56
58
  repl: ## Load up checkoff in pry
57
59
  @bundle exec rake repl
58
60
 
61
+ clean-coverage:
62
+ @bundle exec rake clear_metrics
63
+
64
+ coverage: test report-coverage ## check code coverage
65
+ @bundle exec rake undercover
66
+
67
+ report-coverage: test ## Report summary of coverage to stdout, and generate HTML, XML coverage report
68
+
69
+ report-coverage-to-codecov: report-coverage ## use codecov.io for PR-scoped code coverage reports
70
+ @curl -Os https://uploader.codecov.io/latest/linux/codecov
71
+ @chmod +x codecov
72
+ @./codecov --file coverage/lcov/checkoff.lcov --nonZero
73
+
74
+ # https://github.com/bluelabsio/records-mover/blob/master/Makefile#L25
75
+ cicoverage: report-coverage-to-codecov ## check code coverage, then report to codecov
76
+ @echo "Looking for un-checked-in unit test coverage metrics..."
77
+ @git status --porcelain coverage/.last_run.json
78
+ @git diff coverage/.last_run.json
79
+ @test -z "$(git status --porcelain coverage/.last_run.json)"
80
+
59
81
  update_from_cookiecutter: ## Bring in changes from template project used to create this repo
60
82
  bundle exec overcommit --uninstall
61
83
  IN_COOKIECUTTER_PROJECT_UPGRADER=1 cookiecutter_project_upgrader || true
data/checkoff.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.summary = 'Command-line and gem client for Asana (unofficial)'
14
14
  spec.homepage = 'https://github.com/apiology/checkoff'
15
15
  spec.license = 'MIT license'
16
- spec.required_ruby_version = '>= 2.6'
16
+ spec.required_ruby_version = '>= 2.7'
17
17
 
18
18
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
19
  `git ls-files -z`.split("\x0").reject do |f|
@@ -32,20 +32,26 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  spec.add_development_dependency 'bump'
34
34
  spec.add_development_dependency 'bundler'
35
+ spec.add_development_dependency 'fakeweb'
35
36
  spec.add_development_dependency 'mdl'
36
37
  spec.add_development_dependency 'minitest-profile'
37
- spec.add_development_dependency 'mocha'
38
+ spec.add_development_dependency 'minitest-reporters'
39
+ spec.add_development_dependency 'mocha', ['~> 2.0.0.alpha.1']
38
40
  # 0.58.0 and 0.57.0 don't seem super compatible with signatures, and
39
41
  # magit doesn't seem to want to use the bundled version at the moment,
40
42
  # so let's favor the more recent version...
41
43
  spec.add_development_dependency 'overcommit', ['>=0.58.0']
42
44
  spec.add_development_dependency 'pry'
43
45
  spec.add_development_dependency 'rake', '~> 13.0'
44
- spec.add_development_dependency 'rubocop'
46
+ spec.add_development_dependency 'rspec', '>=3.4'
47
+ spec.add_development_dependency 'rubocop', ['~> 1.36']
45
48
  spec.add_development_dependency 'rubocop-minitest'
46
49
  spec.add_development_dependency 'rubocop-rake'
47
50
  # ensure version with branch coverage
48
51
  spec.add_development_dependency 'simplecov', ['>=0.18.0']
49
52
  spec.add_development_dependency 'simplecov-lcov'
50
53
  spec.add_development_dependency 'undercover'
54
+ spec.metadata = {
55
+ 'rubygems_mfa_required' => 'true',
56
+ }
51
57
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "result": {
3
- "line": 99.56,
4
- "branch": 86.11
3
+ "line": 99.62,
4
+ "branch": 86.42
5
5
  }
6
6
  }
data/fix.sh CHANGED
@@ -69,7 +69,18 @@ ensure_rbenv() {
69
69
 
70
70
  latest_ruby_version() {
71
71
  major_minor=${1}
72
- rbenv install --list 2>/dev/null | grep "^${major_minor}."
72
+
73
+ # Double check that this command doesn't error out under -e
74
+ rbenv install --list >/dev/null 2>&1
75
+
76
+ # not sure why, but 'rbenv install --list' below exits with error code
77
+ # 1...after providing the same output the previous line gave when it
78
+ # exited with error code 0.
79
+ #
80
+ # https://github.com/rbenv/rbenv/issues/1441
81
+ set +e
82
+ rbenv install --list 2>/dev/null | cat | grep "^${major_minor}."
83
+ set -e
73
84
  }
74
85
 
75
86
  ensure_dev_library() {
@@ -91,12 +102,20 @@ ensure_ruby_build_requirements() {
91
102
  ensure_dev_library openssl/ssl.h openssl libssl-dev
92
103
  }
93
104
 
105
+ ensure_latest_ruby_build_definitions() {
106
+ ensure_rbenv
107
+
108
+ git -C "$(rbenv root)"/plugins/ruby-build pull
109
+ }
110
+
94
111
  # You can find out which feature versions are still supported / have
95
112
  # been release here: https://www.ruby-lang.org/en/downloads/
96
113
  ensure_ruby_versions() {
114
+ ensure_latest_ruby_build_definitions
115
+
97
116
  # You can find out which feature versions are still supported / have
98
117
  # been release here: https://www.ruby-lang.org/en/downloads/
99
- ruby_versions="$(latest_ruby_version 2.6)"
118
+ ruby_versions="$(latest_ruby_version 2.7)"
100
119
 
101
120
  echo "Latest Ruby versions: ${ruby_versions}"
102
121
 
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'custom_field_variant'
4
+
5
+ module Checkoff
6
+ module Internal
7
+ module SearchUrl
8
+ # Convert custom field parameters from an Asana search URL into
9
+ # API search arguments and Checkoff task selectors
10
+ class CustomFieldParamConverter
11
+ def initialize(custom_field_params:)
12
+ @custom_field_params = custom_field_params
13
+ end
14
+
15
+ def convert
16
+ args = {}
17
+ task_selector = []
18
+ by_custom_field.each do |gid, single_custom_field_params|
19
+ new_args, new_task_selector = convert_single_custom_field_params(gid,
20
+ single_custom_field_params)
21
+ args, task_selector = merge_args_and_task_selectors(args, new_args,
22
+ task_selector, new_task_selector)
23
+ end
24
+ [args, task_selector]
25
+ end
26
+
27
+ private
28
+
29
+ def by_custom_field
30
+ custom_field_params.group_by do |key, _value|
31
+ gid_from_custom_field_key(key)
32
+ end.transform_values(&:to_h)
33
+ end
34
+
35
+ def merge_args_and_task_selectors(args, new_args, task_selector, new_task_selector)
36
+ args = args.merge(new_args)
37
+ return [args, task_selector] if new_task_selector == []
38
+
39
+ raise 'Teach me how to merge task selectors' unless task_selector == []
40
+
41
+ task_selector = new_task_selector
42
+
43
+ [args, task_selector]
44
+ end
45
+
46
+ VARIANTS = {
47
+ 'is' => CustomFieldVariant::Is,
48
+ 'no_value' => CustomFieldVariant::NoValue,
49
+ 'is_not' => CustomFieldVariant::IsNot,
50
+ 'less_than' => CustomFieldVariant::LessThan,
51
+ }.freeze
52
+
53
+ def convert_single_custom_field_params(gid, single_custom_field_params)
54
+ variant_key = "custom_field_#{gid}.variant"
55
+ variant = single_custom_field_params.fetch(variant_key)
56
+ remaining_params = single_custom_field_params.reject { |k, _v| k == variant_key }
57
+ raise "Teach me how to handle #{variant_key} = #{variant}" unless variant.length == 1
58
+
59
+ variant_class = VARIANTS[variant[0]]
60
+ return variant_class.new(gid, remaining_params).convert unless variant_class.nil?
61
+
62
+ raise "Teach me how to handle #{variant_key} = #{variant}"
63
+ end
64
+
65
+ def gid_from_custom_field_key(key)
66
+ key.split('_')[2].split('.')[0]
67
+ end
68
+
69
+ attr_reader :custom_field_params
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkoff
4
+ module Internal
5
+ module SearchUrl
6
+ module CustomFieldVariant
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 LessThan < 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 IsNot < 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 NoValue < 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 Is < 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
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'simple_param_converter'
6
+ require_relative 'custom_field_param_converter'
7
+
8
+ module Checkoff
9
+ module Internal
10
+ module SearchUrl
11
+ # Parse Asana search URLs into parameters suitable to pass into
12
+ # the /workspaces/{workspace_gid}/tasks/search endpoint
13
+ class Parser
14
+ def initialize(_deps = {}); end
15
+
16
+ def convert_params(url)
17
+ url_params = CGI.parse(URI.parse(url).query)
18
+ custom_field_params, simple_url_params = partition_url_params(url_params)
19
+ custom_field_args, task_selector = convert_custom_field_params(custom_field_params)
20
+ simple_url_args = convert_simple_params(simple_url_params)
21
+ [custom_field_args.merge(simple_url_args), task_selector]
22
+ end
23
+
24
+ private
25
+
26
+ def convert_simple_params(simple_url_params)
27
+ SimpleParamConverter.new(simple_url_params: simple_url_params).convert
28
+ end
29
+
30
+ def convert_custom_field_params(custom_field_params)
31
+ CustomFieldParamConverter.new(custom_field_params: custom_field_params).convert
32
+ end
33
+
34
+ def partition_url_params(url_params)
35
+ url_params.to_a.partition do |key, _values|
36
+ key.start_with? 'custom_field_'
37
+ end.map(&:to_h)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'custom_field_param_converter'
4
+
5
+ module Checkoff
6
+ module Internal
7
+ module SearchUrl
8
+ module SimpleParam
9
+ # base class for handling different types of search url params
10
+ class SimpleParam
11
+ def initialize(values:)
12
+ @values = values
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :values
18
+ end
19
+
20
+ # Handle 'any_projects.ids' search url param
21
+ class AnyProjectsIds < SimpleParam
22
+ def convert
23
+ ['projects.any', values.join(',')]
24
+ end
25
+ end
26
+
27
+ # Handle 'completion' search url param
28
+ class Completion < SimpleParam
29
+ def convert
30
+ raise "Teach me how to handle #{key} = #{values}" if values.length != 1
31
+
32
+ value = values.fetch(0)
33
+ raise "Teach me how to handle #{key} = #{values}" if value != 'incomplete'
34
+
35
+ ['completed', false]
36
+ end
37
+ end
38
+
39
+ # Handle 'not_tags.ids' search url param
40
+ class NotTagsIds < SimpleParam
41
+ def convert
42
+ raise "Teach me how to handle #{key} = #{values}" if values.length != 1
43
+
44
+ value = values.fetch(0)
45
+ tag_ids = value.split('~')
46
+ ['tags.not', tag_ids.join(',')]
47
+ end
48
+ end
49
+ end
50
+
51
+ # Convert simple parameters - ones where the param name itself
52
+ # doesn't encode any parameters'
53
+ class SimpleParamConverter
54
+ def initialize(simple_url_params:)
55
+ @simple_url_params = simple_url_params
56
+ end
57
+
58
+ def convert
59
+ simple_url_params.to_a.to_h do |key, values|
60
+ convert_arg(key, values)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ ARGS = {
67
+ 'any_projects.ids' => SimpleParam::AnyProjectsIds,
68
+ 'completion' => SimpleParam::Completion,
69
+ 'not_tags.ids' => SimpleParam::NotTagsIds,
70
+ }.freeze
71
+
72
+ # https://developers.asana.com/docs/search-tasks-in-a-workspace
73
+ def convert_arg(key, values)
74
+ clazz = ARGS.fetch(key)
75
+ clazz.new(values: values).convert
76
+ end
77
+
78
+ attr_reader :simple_url_params
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'search_url/parser'
@@ -41,7 +41,7 @@ module Checkoff
41
41
  def raw_subtasks(task)
42
42
  task_options = projects.task_options
43
43
  task_options[:options][:fields] << 'is_rendered_as_separator'
44
- task.subtasks(task_options)
44
+ task.subtasks(**task_options)
45
45
  end
46
46
  cache_method :raw_subtasks, SHORT_CACHE_TIME
47
47
 
@@ -12,7 +12,7 @@ require_relative 'task_selectors'
12
12
  require 'asana/resource_includes/collection'
13
13
  require 'asana/resource_includes/response_helper'
14
14
 
15
- require 'checkoff/internal/search_url_parser'
15
+ require 'checkoff/internal/search_url'
16
16
 
17
17
  # https://developers.asana.com/docs/task-searches
18
18
  module Checkoff
@@ -33,7 +33,7 @@ module Checkoff
33
33
  projects: Checkoff::Projects.new(config: config),
34
34
  clients: Checkoff::Clients.new(config: config),
35
35
  client: clients.client,
36
- search_url_parser: Checkoff::Internal::SearchUrlParser.new,
36
+ search_url_parser: Checkoff::Internal::SearchUrl::Parser.new,
37
37
  asana_resources_collection_class: Asana::Resources::Collection)
38
38
  @workspaces = workspaces
39
39
  @task_selectors = task_selectors
@@ -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.19.2'
6
+ VERSION = '0.20.0'
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.19.2
4
+ version: 0.20.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: 2022-07-24 00:00:00.000000000 Z
11
+ date: 2022-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: fakeweb
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: mdl
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -137,7 +151,7 @@ dependencies:
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
- name: mocha
154
+ name: minitest-reporters
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
157
  - - ">="
@@ -150,6 +164,20 @@ dependencies:
150
164
  - - ">="
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: mocha
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 2.0.0.alpha.1
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 2.0.0.alpha.1
153
181
  - !ruby/object:Gem::Dependency
154
182
  name: overcommit
155
183
  requirement: !ruby/object:Gem::Requirement
@@ -193,19 +221,33 @@ dependencies:
193
221
  - !ruby/object:Gem::Version
194
222
  version: '13.0'
195
223
  - !ruby/object:Gem::Dependency
196
- name: rubocop
224
+ name: rspec
197
225
  requirement: !ruby/object:Gem::Requirement
198
226
  requirements:
199
227
  - - ">="
200
228
  - !ruby/object:Gem::Version
201
- version: '0'
229
+ version: '3.4'
202
230
  type: :development
203
231
  prerelease: false
204
232
  version_requirements: !ruby/object:Gem::Requirement
205
233
  requirements:
206
234
  - - ">="
207
235
  - !ruby/object:Gem::Version
208
- version: '0'
236
+ version: '3.4'
237
+ - !ruby/object:Gem::Dependency
238
+ name: rubocop
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: '1.36'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - "~>"
249
+ - !ruby/object:Gem::Version
250
+ version: '1.36'
209
251
  - !ruby/object:Gem::Dependency
210
252
  name: rubocop-minitest
211
253
  requirement: !ruby/object:Gem::Requirement
@@ -322,7 +364,11 @@ files:
322
364
  - lib/checkoff/custom_fields.rb
323
365
  - lib/checkoff/internal/config_loader.rb
324
366
  - lib/checkoff/internal/create-class.sh
325
- - lib/checkoff/internal/search_url_parser.rb
367
+ - lib/checkoff/internal/search_url.rb
368
+ - lib/checkoff/internal/search_url/custom_field_param_converter.rb
369
+ - lib/checkoff/internal/search_url/custom_field_variant.rb
370
+ - lib/checkoff/internal/search_url/parser.rb
371
+ - lib/checkoff/internal/search_url/simple_param_converter.rb
326
372
  - lib/checkoff/internal/task_selector_evaluator.rb
327
373
  - lib/checkoff/my_tasks.rb
328
374
  - lib/checkoff/projects.rb
@@ -366,7 +412,8 @@ files:
366
412
  homepage: https://github.com/apiology/checkoff
367
413
  licenses:
368
414
  - MIT license
369
- metadata: {}
415
+ metadata:
416
+ rubygems_mfa_required: 'true'
370
417
  post_install_message:
371
418
  rdoc_options: []
372
419
  require_paths:
@@ -375,14 +422,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
375
422
  requirements:
376
423
  - - ">="
377
424
  - !ruby/object:Gem::Version
378
- version: '2.6'
425
+ version: '2.7'
379
426
  required_rubygems_version: !ruby/object:Gem::Requirement
380
427
  requirements:
381
428
  - - ">="
382
429
  - !ruby/object:Gem::Version
383
430
  version: '0'
384
431
  requirements: []
385
- rubygems_version: 3.0.3.1
432
+ rubygems_version: 3.2.33
386
433
  signing_key:
387
434
  specification_version: 4
388
435
  summary: Command-line and gem client for Asana (unofficial)
@@ -1,228 +0,0 @@
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
- when 'not_tags.ids'
217
- raise "Teach me how to handle #{key} = #{values}" if values.length != 1
218
-
219
- value = values.fetch(0)
220
- tag_ids = value.split('~')
221
- ['tags.not', tag_ids.join(',')]
222
- else
223
- raise "Teach me how to handle #{key} = #{values}"
224
- end
225
- end
226
- end
227
- end
228
- end