checkoff 0.19.1 → 0.20.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: b6a571f1d0bef00f4e030c22ea9a1cfae923c5253f8949deeaa24d537709c723
4
- data.tar.gz: 1545211bc1d508f12fd26769d41201818ea280714c96c685ca31148950ba56f9
3
+ metadata.gz: ca8acc9a67554659cd34a8a9a60f539e834088bc896ef6e3bb7761e75183ac8a
4
+ data.tar.gz: ff10088e95968a73ccb5590b316a4b6e3f9ff49572ab87271f710bc9a02f25b3
5
5
  SHA512:
6
- metadata.gz: '08f83449b8628676c61c2afc08a9723b0639572e42c646e2fd81dc8571f446ac5a5e46ccbc58ce3ea2dddc5538ac7736081f5efde01bf46fc3b2111872f7fa93'
7
- data.tar.gz: ddefd4b5d02bb05a610e0ead08c3126c76f9373ceb6a339ac61777a997de753b25491447df52c4127b96bf4fb8dac01f9e57183d82da3c61f121c629f0e1a757
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.1)
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.61,
4
- "branch": 86.52
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.1'
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.1
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,220 +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
- end
217
- end
218
- end
219
- end
220
- end