checkoff 0.10.0 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 121d92b56f0936e25ff98efd5f601e9fea6cf6a71191f0ba7a2803785bcc1feb
4
- data.tar.gz: e3b871d18fa8263e0d61df92e22b59a369b5f78e527a8da57ac0fb472db5c34d
3
+ metadata.gz: c9229d2827676a7888a6bb60059d06729ec2ba27912e029ac452827c6c2ce4e2
4
+ data.tar.gz: f15c045ba7aa21087c5c3b179866c17632757ef7a1923c7c9a1c4185d4828043
5
5
  SHA512:
6
- metadata.gz: '0862ee2cd9aababfb015020a1c55e6f7a5027bc001b2ec6317b661b7407fcb095bb981f7d44ad251a50166edf674bc798f3629b986cece29eb30286f7be09fd0'
7
- data.tar.gz: ebaed1748125699b93f3804ee79d2c33fdf560aa232e904a69fb1e55ad3520802096b35ea9a921e5b70c67f37a6c78ce3444606333e6ed81ca6f7e4912ae61b6
6
+ metadata.gz: d711b94cb4ae8b0933ed9c0e20104123e7707b898616335ddcb73e0f30e09a2bd427889f554a6422b7071ebbf304c58832446783288f1b4d1171a1b31a61dd51
7
+ data.tar.gz: cc6e96a3371f0870777228394ca085db4a7786badf3173b49a864c79866af3ad4ca71a3482920b1de8f8e9f5b6683d9f846ffd7cd819bc81de20a01cfdc83a3c
data/.circleci/config.yml CHANGED
@@ -19,7 +19,7 @@ commands:
19
19
  export PATH="${HOME}/.pyenv/bin:${PATH}"
20
20
  export PATH="${HOME}/.rbenv/bin:${HOME}/.rbenv/shims:${PATH}"
21
21
  export PATH="${HOME}/project/node_modules/.bin:${PATH}"
22
- eval "$(pyenv init -)"
22
+ eval "$(pyenv init --path)"
23
23
  eval "$(pyenv virtualenv-init -)"
24
24
  eval "$(rbenv init -)"
25
25
  export BUNDLE_PATH=vendor/bundle
@@ -97,11 +97,12 @@ jobs:
97
97
  steps:
98
98
  - set_up_environment
99
99
  - run_with_languages:
100
- label: Run tests
100
+ label: Test
101
101
  command: |
102
- make test
102
+ bundle exec rake citest
103
103
  # https://github.com/bluelabsio/records-mover/blob/master/Makefile#L25
104
104
  git status --porcelain coverage/.last_run.json
105
+ git diff coverage/.last_run.json
105
106
  test -z "$(git status --porcelain coverage/.last_run.json)"
106
107
 
107
108
  workflows:
data/.envrc CHANGED
@@ -1,2 +1,2 @@
1
1
  PATH_add bin
2
- eval $(with-op op-env sh -e ASANA__PERSONAL_ACCESS_TOKEN)
2
+ eval $(with-op op-env sh -e ASANA__PERSONAL_ACCESS_TOKEN -e ASANA__DEFAULT_ASSIGNEE_GID)
data/.gitignore CHANGED
@@ -58,4 +58,5 @@ requirements_dev.txt.installed
58
58
  /coverage/.resultset.json.lock
59
59
  /coverage/assets/
60
60
  /coverage/index.html
61
+ /coverage/lcov/
61
62
  /pkg
data/Gemfile.lock CHANGED
@@ -1,16 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- checkoff (0.10.0)
4
+ checkoff (0.12.1)
5
5
  activesupport
6
6
  asana (> 0.10.0)
7
7
  cache_method
8
8
  dalli
9
+ gli
9
10
 
10
11
  GEM
11
12
  remote: https://rubygems.org/
12
13
  specs:
13
- activesupport (6.1.3.2)
14
+ activesupport (6.1.4)
14
15
  concurrent-ruby (~> 1.0, >= 1.0.2)
15
16
  i18n (>= 1.6, < 2)
16
17
  minitest (>= 5.1)
@@ -27,23 +28,32 @@ GEM
27
28
  cache_method (0.2.7)
28
29
  cache (>= 0.2.1)
29
30
  childprocess (4.0.0)
30
- concurrent-ruby (1.1.8)
31
+ concurrent-ruby (1.1.9)
31
32
  dalli (2.7.11)
32
- docile (1.3.4)
33
- faraday (1.4.1)
33
+ docile (1.4.0)
34
+ faraday (1.5.1)
35
+ faraday-em_http (~> 1.0)
36
+ faraday-em_synchrony (~> 1.0)
34
37
  faraday-excon (~> 1.1)
38
+ faraday-httpclient (~> 1.0.1)
35
39
  faraday-net_http (~> 1.0)
36
40
  faraday-net_http_persistent (~> 1.1)
41
+ faraday-patron (~> 1.0)
37
42
  multipart-post (>= 1.2, < 3)
38
43
  ruby2_keywords (>= 0.0.4)
44
+ faraday-em_http (1.0.0)
45
+ faraday-em_synchrony (1.0.0)
39
46
  faraday-excon (1.1.0)
47
+ faraday-httpclient (1.0.1)
40
48
  faraday-net_http (1.0.1)
41
- faraday-net_http_persistent (1.1.0)
49
+ faraday-net_http_persistent (1.2.0)
50
+ faraday-patron (1.0.0)
42
51
  faraday_middleware (1.0.0)
43
52
  faraday (~> 1.0)
44
53
  faraday_middleware-multi_json (0.0.6)
45
54
  faraday_middleware
46
55
  multi_json
56
+ gli (2.20.1)
47
57
  i18n (1.8.10)
48
58
  concurrent-ruby (~> 1.0)
49
59
  imagen (0.1.8)
@@ -62,9 +72,10 @@ GEM
62
72
  multi_json (~> 1.3)
63
73
  multi_xml (~> 0.5)
64
74
  rack (>= 1.2, < 3)
65
- overcommit (0.57.0)
75
+ overcommit (0.58.0)
66
76
  childprocess (>= 0.6.3, < 5)
67
77
  iniparse (~> 1.4)
78
+ rexml (~> 3.2)
68
79
  parallel (1.20.1)
69
80
  parser (3.0.1.1)
70
81
  ast (~> 2.4.1)
@@ -73,7 +84,7 @@ GEM
73
84
  rake (13.0.3)
74
85
  regexp_parser (2.1.1)
75
86
  rexml (3.2.5)
76
- rubocop (1.14.0)
87
+ rubocop (1.15.0)
77
88
  parallel (~> 1.10)
78
89
  parser (>= 3.0.0.0)
79
90
  rainbow (>= 2.2.2, < 4.0)
@@ -89,13 +100,14 @@ GEM
89
100
  rubocop-rake (0.5.1)
90
101
  rubocop
91
102
  ruby-progressbar (1.11.0)
92
- ruby2_keywords (0.0.4)
103
+ ruby2_keywords (0.0.5)
93
104
  rugged (1.1.0)
94
105
  simplecov (0.21.2)
95
106
  docile (~> 1.1)
96
107
  simplecov-html (~> 0.11)
97
108
  simplecov_json_formatter (~> 0.1)
98
109
  simplecov-html (0.12.3)
110
+ simplecov-lcov (0.8.0)
99
111
  simplecov_json_formatter (0.1.3)
100
112
  tzinfo (2.0.4)
101
113
  concurrent-ruby (~> 1.0)
@@ -118,13 +130,14 @@ DEPENDENCIES
118
130
  checkoff!
119
131
  minitest-profile
120
132
  mocha
121
- overcommit
133
+ overcommit (>= 0.58.0)
122
134
  rake (~> 13.0)
123
135
  rubocop
124
136
  rubocop-minitest
125
137
  rubocop-rake
126
138
  simplecov
139
+ simplecov-lcov
127
140
  undercover
128
141
 
129
142
  BUNDLED WITH
130
- 2.2.11
143
+ 2.2.24
data/Makefile CHANGED
@@ -1,4 +1,4 @@
1
- .PHONY: clean test help quality localtest spec feature
1
+ .PHONY: clean test help quality localtest
2
2
  .DEFAULT_GOAL := default
3
3
 
4
4
  define PRINT_HELP_PYSCRIPT
@@ -32,7 +32,10 @@ Gemfile.lock.installed: Gemfile.lock
32
32
 
33
33
  bundle_install: Gemfile.lock.installed ## Install Ruby dependencies
34
34
 
35
- clean: ## remove all built artifacts
35
+ clear_metrics: ## remove or reset result artifacts created by tests and quality tools
36
+ bundle exec rake clear_metrics
37
+
38
+ clean: clear_metrics ## remove all built artifacts
36
39
 
37
40
  quality: ## run precommit quality checks
38
41
  bundle exec overcommit --run
@@ -40,7 +43,8 @@ quality: ## run precommit quality checks
40
43
  test: ## Run lower-level tests
41
44
  @bundle exec rake test
42
45
 
43
- localtest: test quality feature ## run default local actions
46
+ localtest: ## run default local actions
47
+ @bundle exec rake localtest
44
48
 
45
49
  update_from_cookiecutter: ## Bring in changes from template project used to create this repo
46
50
  bundle exec overcommit --uninstall
data/README.md CHANGED
@@ -12,7 +12,7 @@ $ checkoff --help
12
12
  View tasks:
13
13
  checkoff view workspace project [section]
14
14
 
15
- 'project' can be set to a project name, or :my_tasks, :my_tasks_upcoming, :my_tasks_new, or :my_tasks_today
15
+ 'project' can be set to a project name, or :my_tasks for your user task list.
16
16
  ```
17
17
 
18
18
  Let's say we have a project like this:
data/checkoff.gemspec CHANGED
@@ -26,16 +26,21 @@ Gem::Specification.new do |spec|
26
26
  spec.add_runtime_dependency 'asana', '>0.10.0'
27
27
  spec.add_runtime_dependency 'cache_method'
28
28
  spec.add_runtime_dependency 'dalli'
29
+ spec.add_runtime_dependency 'gli'
29
30
 
30
31
  spec.add_development_dependency 'bump'
31
32
  spec.add_development_dependency 'bundler'
32
33
  spec.add_development_dependency 'minitest-profile'
33
34
  spec.add_development_dependency 'mocha'
34
- spec.add_development_dependency 'overcommit'
35
+ # 0.58.0 and 0.57.0 don't seem super compatible with signatures, and
36
+ # magit doesn't seem to want to use the bundled version at the moment,
37
+ # so let's favor the more recent version...
38
+ spec.add_development_dependency 'overcommit', ['>=0.58.0']
35
39
  spec.add_development_dependency 'rake', '~> 13.0'
36
40
  spec.add_development_dependency 'rubocop'
37
41
  spec.add_development_dependency 'rubocop-minitest'
38
42
  spec.add_development_dependency 'rubocop-rake'
39
43
  spec.add_development_dependency 'simplecov'
44
+ spec.add_development_dependency 'simplecov-lcov'
40
45
  spec.add_development_dependency 'undercover'
41
46
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "result": {
3
- "line": 93.15,
4
- "branch": 61.66
3
+ "line": 97.56,
4
+ "branch": 85.71
5
5
  }
6
6
  }
data/exe/checkoff CHANGED
@@ -4,5 +4,4 @@
4
4
  require 'checkoff/cli'
5
5
 
6
6
  CacheMethod.config.storage = Dalli::Client.new('memcached:11211')
7
- cli = Checkoff::CLI.new
8
- puts cli.run(ARGV)
7
+ exit(Checkoff::CheckoffGLIApp.run(ARGV))
data/fix.sh CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  set -o pipefail
4
4
 
5
+ apt_upgraded=0
6
+
7
+ update_apt() {
8
+ if [ "${apt_upgraded}" = 0 ]
9
+ then
10
+ sudo apt-get update -y
11
+ apt_upgraded=1
12
+ fi
13
+ }
14
+
5
15
  install_rbenv() {
6
16
  if [ "$(uname)" == "Darwin" ]
7
17
  then
@@ -77,6 +87,8 @@ ensure_dev_library() {
77
87
 
78
88
  ensure_ruby_build_requirements() {
79
89
  ensure_dev_library readline/readline.h readline libreadline-dev
90
+ ensure_dev_library zlib.h zlib zlib1g-dev
91
+ ensure_dev_library openssl/ssl.h openssl libssl-dev
80
92
  }
81
93
 
82
94
  # You can find out which feature versions are still supported / have
@@ -92,7 +104,17 @@ ensure_ruby_versions() {
92
104
 
93
105
  for ver in $ruby_versions
94
106
  do
95
- rbenv install -s "${ver}"
107
+ # These CFLAGS can be retired once 2.6.7 is no longer needed :
108
+ #
109
+ # https://github.com/rbenv/ruby-build/issues/1747
110
+ # https://github.com/rbenv/ruby-build/issues/1489
111
+ # https://bugs.ruby-lang.org/issues/17777
112
+ if [ "${ver}" == 2.6.7 ]
113
+ then
114
+ CFLAGS="-Wno-error=implicit-function-declaration" rbenv install -s "${ver}"
115
+ else
116
+ rbenv install -s "${ver}"
117
+ fi
96
118
  done
97
119
  }
98
120
 
@@ -101,7 +123,17 @@ ensure_bundle() {
101
123
  #
102
124
  # https://app.circleci.com/pipelines/github/apiology/source_finder/21/workflows/88db659f-a4f4-4751-abc0-46f5929d8e58/jobs/107
103
125
  set_rbenv_env_variables
104
- bundle --version >/dev/null 2>&1 || gem install bundler
126
+ bundle --version >/dev/null 2>&1 || gem install --no-document bundler
127
+ bundler_version=$(bundle --version | cut -d ' ' -f3)
128
+ bundler_version_major=$(cut -d. -f1 <<< "${bundler_version}")
129
+ bundler_version_minor=$(cut -d. -f2 <<< "${bundler_version}")
130
+ # Version 2.1 of bundler seems to have some issues with nokogiri:
131
+ #
132
+ # https://app.asana.com/0/1107901397356088/1199504270687298
133
+ if [ "${bundler_version_major}" == 2 ] && [ "${bundler_version_minor}" -lt 2 ]
134
+ then
135
+ gem install --no-document bundler
136
+ fi
105
137
  make bundle_install
106
138
  # https://bundler.io/v2.0/bundle_lock.html#SUPPORTING-OTHER-PLATFORMS
107
139
  #
@@ -138,8 +170,9 @@ WARNING: seems you still have not added 'pyenv' to the load path.
138
170
  # Load pyenv automatically by adding
139
171
  # the following to ~/.bashrc:
140
172
 
141
- export PATH="$HOME/.pyenv/bin:$PATH"
142
- eval "$(pyenv init -)"
173
+ export PYENV_ROOT="${HOME}/.pyenv"
174
+ export PATH="${PYENV_ROOT}/bin:$PATH"
175
+ eval "$(pyenv init --path)"
143
176
  eval "$(pyenv virtualenv-init -)"
144
177
  EOF
145
178
  fi
@@ -153,8 +186,9 @@ set_pyenv_env_variables() {
153
186
  #
154
187
  # https://app.circleci.com/pipelines/github/apiology/cookiecutter-pypackage/15/workflows/10506069-7662-46bd-b915-2992db3f795b/jobs/15
155
188
  set +u
156
- export PATH="${HOME}/.pyenv/bin:$PATH"
157
- eval "$(pyenv init -)"
189
+ export PYENV_ROOT="${HOME}/.pyenv"
190
+ export PATH="${PYENV_ROOT}/bin:$PATH"
191
+ eval "$(pyenv init --path)"
158
192
  eval "$(pyenv virtualenv-init -)"
159
193
  set -u
160
194
  }
@@ -179,7 +213,7 @@ install_package() {
179
213
  HOMEBREW_NO_AUTO_UPDATE=1 brew install "${homebrew_package}"
180
214
  elif type apt-get >/dev/null 2>&1
181
215
  then
182
- sudo apt-get update -y
216
+ update_apt
183
217
  sudo apt-get install -y "${apt_package}"
184
218
  else
185
219
  >&2 echo "Teach me how to install packages on this plaform"
@@ -192,6 +226,7 @@ ensure_python_build_requirements() {
192
226
  ensure_dev_library bzlib.h bzip2 libbz2-dev
193
227
  ensure_dev_library openssl/ssl.h openssl libssl-dev
194
228
  ensure_dev_library ffi.h libffi libffi-dev
229
+ ensure_dev_library sqlite3.h sqlite3 libsqlite3-dev
195
230
  }
196
231
 
197
232
  # You can find out which feature versions are still supported / have
data/lib/checkoff/cli.rb CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  require 'ostruct'
6
6
  require 'dalli'
7
+ require 'gli'
7
8
  require 'cache_method'
8
9
  require_relative 'workspaces'
9
10
  require_relative 'projects'
@@ -11,41 +12,44 @@ require_relative 'tasks'
11
12
  require_relative 'sections'
12
13
 
13
14
  module Checkoff
14
- # Provide ability for CLI to pull Asana items
15
- # rubocop:disable Metrics/ClassLength
16
- class CLI
17
- attr_reader :sections, :stderr
18
-
19
- def initialize(config: Checkoff::ConfigLoader.load(:asana),
20
- workspaces: Checkoff::Workspaces.new(config: config),
15
+ # CLI subcommand that shows tasks in JSON form
16
+ class ViewSubcommand
17
+ def initialize(workspace_name, project_name, section_name,
18
+ task_name,
19
+ config: Checkoff::ConfigLoader.load(:asana),
21
20
  projects: Checkoff::Projects.new(config: config),
22
21
  sections: Checkoff::Sections.new(config: config,
23
22
  projects: projects),
24
- tasks: Checkoff::Tasks.new(config: config),
25
- stderr: $stderr,
26
- kernel: Kernel)
27
- @workspaces = workspaces
28
- @projects = projects
23
+ tasks: Checkoff::Tasks.new(config: config,
24
+ sections: sections),
25
+ stderr: $stderr)
26
+ @workspace_name = workspace_name
27
+ @stderr = stderr
28
+ validate_and_assign_project_name(project_name)
29
+ @section_name = section_name
30
+ @task_name = task_name
29
31
  @sections = sections
30
32
  @tasks = tasks
31
- @kernel = kernel
32
- @stderr = stderr
33
33
  end
34
34
 
35
- def task_to_hash(task)
36
- task_out = {
37
- name: task.name,
38
- }
39
- if task.due_on
40
- task_out[:due] = task.due_on
41
- elsif task.due_at
42
- task_out[:due] = task.due_at
35
+ def run
36
+ if section_name.nil?
37
+ run_on_project(workspace_name, project_name)
38
+ elsif task_name.nil?
39
+ run_on_section(workspace_name, project_name, section_name)
40
+ else
41
+ run_on_task(workspace_name, project_name, section_name, task_name)
43
42
  end
44
- task_out
45
43
  end
46
44
 
47
- def tasks_to_hash(tasks)
48
- tasks.map { |task| task_to_hash(task) }
45
+ private
46
+
47
+ def validate_and_assign_project_name(project_name)
48
+ @project_name = if project_name.start_with? ':'
49
+ project_name[1..].to_sym
50
+ else
51
+ project_name
52
+ end
49
53
  end
50
54
 
51
55
  def run_on_project(workspace, project)
@@ -63,80 +67,89 @@ module Checkoff
63
67
  tasks_to_hash(tasks).to_json
64
68
  end
65
69
 
66
- def quickadd(workspace_name, task_name)
67
- workspace = @workspaces.workspace_by_name(workspace_name)
68
- @tasks.add_task(task_name,
69
- workspace_gid: workspace.gid)
70
+ def run_on_task(workspace, project, section, task_name)
71
+ section = nil if section == ''
72
+ task = tasks.task(workspace, project, task_name, section_name: section)
73
+ task_to_hash(task).to_json
70
74
  end
71
75
 
72
- def validate_args!(args)
73
- return unless args.length < 2 || !%w[view quickadd].include?(args[0])
74
-
75
- output_help
76
- exit(1)
76
+ def task_to_hash(task)
77
+ task_out = {
78
+ name: task.name,
79
+ }
80
+ if task.due_on
81
+ task_out[:due] = task.due_on
82
+ elsif task.due_at
83
+ task_out[:due] = task.due_at
84
+ end
85
+ task_out
77
86
  end
78
87
 
79
- def parse_view_args(subargs, args)
80
- subargs.workspace = args[1]
81
- subargs.project = args[2]
82
- subargs.section = args[3]
88
+ def tasks_to_hash(tasks)
89
+ tasks.map { |task| task_to_hash(task) }
83
90
  end
84
91
 
85
- def parse_quickadd_args(subargs, args)
86
- subargs.workspace = args[1]
87
- subargs.task_name = args[2]
88
- end
92
+ attr_reader :workspace_name, :project_name, :section_name, :task_name, :sections, :tasks, :stderr
93
+ end
89
94
 
90
- # rubocop:disable Metrics/MethodLength
91
- def parse_args(args)
92
- mode = args[0]
93
- subargs = OpenStruct.new
94
- case mode
95
- when 'view'
96
- parse_view_args(subargs, args)
97
- when 'quickadd'
98
- parse_quickadd_args(subargs, args)
99
- else
100
- raise
101
- end
102
- [mode, subargs]
95
+ # CLI subcommand that creates a task
96
+ class QuickaddSubcommand
97
+ def initialize(workspace_name, task_name,
98
+ config: Checkoff::ConfigLoader.load(:asana),
99
+ workspaces: Checkoff::Workspaces.new(config: config),
100
+ tasks: Checkoff::Tasks.new(config: config))
101
+ @workspace_name = workspace_name
102
+ @task_name = task_name
103
+ @workspaces = workspaces
104
+ @tasks = tasks
103
105
  end
104
- # rubocop:enable Metrics/MethodLength
105
-
106
- def output_help
107
- stderr.puts 'View tasks:'
108
- stderr.puts " #{$PROGRAM_NAME} view workspace project [section]"
109
- stderr.puts " #{$PROGRAM_NAME} quickadd workspace task_name"
110
- stderr.puts
111
- stderr.puts "'project' can be set to a project name, or :my_tasks, " \
112
- ":my_tasks_upcoming, :my_tasks_new, or :my_tasks_today"
106
+
107
+ def run
108
+ workspace = @workspaces.workspace_by_name(workspace_name)
109
+ @tasks.add_task(task_name,
110
+ workspace_gid: workspace.gid)
113
111
  end
114
112
 
115
- def view(workspace_name, project_name, section_name)
116
- if project_name.nil?
117
- stderr.puts 'Please specify a project name'
118
- exit(1)
119
- end
120
- project_name = project_name[1..].to_sym if project_name.start_with? ':'
121
- if section_name.nil?
122
- run_on_project(workspace_name, project_name)
123
- else
124
- run_on_section(workspace_name, project_name, section_name)
113
+ private
114
+
115
+ attr_reader :workspace_name, :task_name
116
+ end
117
+
118
+ # Provide ability for CLI to pull Asana items
119
+ class CheckoffGLIApp
120
+ extend GLI::App
121
+
122
+ program_desc 'Command-line client for Asana (unofficial)'
123
+
124
+ subcommand_option_handling :normal
125
+ arguments :strict
126
+
127
+ desc 'Add a short task to Asana'
128
+ arg 'workspace'
129
+ arg 'task_name'
130
+ command :quickadd do |c|
131
+ c.action do |_global_options, _options, args|
132
+ workspace_name = args.fetch(0)
133
+ task_name = args.fetch(1)
134
+
135
+ QuickaddSubcommand.new(workspace_name, task_name).run
125
136
  end
126
137
  end
127
138
 
128
- def run(args)
129
- validate_args!(args)
130
- command, subargs = parse_args(args)
131
- case command
132
- when 'view'
133
- view(subargs.workspace, subargs.project, subargs.section)
134
- when 'quickadd'
135
- quickadd(subargs.workspace, subargs.task_name)
136
- else
137
- raise
139
+ desc 'Output representation of Asana tasks'
140
+ arg 'workspace'
141
+ arg 'project'
142
+ arg 'section', :optional
143
+ arg 'task_name', :optional
144
+ command :view do |c|
145
+ c.action do |_global_options, _options, args|
146
+ workspace_name = args.fetch(0)
147
+ project_name = args.fetch(1)
148
+ section_name = args[2]
149
+ task_name = args[3]
150
+
151
+ puts ViewSubcommand.new(workspace_name, project_name, section_name, task_name).run
138
152
  end
139
153
  end
140
154
  end
141
- # rubocop:enable Metrics/ClassLength
142
155
  end
@@ -13,10 +13,6 @@ module Checkoff
13
13
  @yaml_filename = yaml_filename
14
14
  end
15
15
 
16
- def envvar_name(key)
17
- "#{@envvar_prefix}__#{key.upcase}"
18
- end
19
-
20
16
  def [](key)
21
17
  config_value = @config[key]
22
18
  return config_value unless config_value.nil?
@@ -31,25 +27,35 @@ module Checkoff
31
27
  raise KeyError,
32
28
  "Please configure either the #{key} key in #{@yaml_filename} or set #{envvar_name(key)}"
33
29
  end
30
+
31
+ private
32
+
33
+ def envvar_name(key)
34
+ "#{@envvar_prefix}__#{key.upcase}"
35
+ end
34
36
  end
35
37
 
36
38
  # Load configuration file
37
39
  class ConfigLoader
38
- def self.yaml_filename(sym)
39
- file = "#{sym}.yml"
40
- File.expand_path("~/.#{file}")
41
- end
40
+ class << self
41
+ def load(sym)
42
+ yaml_result = load_yaml_file(sym)
43
+ EnvFallbackConfigLoader.new(yaml_result, sym, yaml_filename(sym))
44
+ end
42
45
 
43
- def self.load_yaml_file(sym)
44
- filename = yaml_filename(sym)
45
- return {} unless File.exist?(filename)
46
+ private
46
47
 
47
- YAML.load_file(filename).with_indifferent_access
48
- end
48
+ def load_yaml_file(sym)
49
+ filename = yaml_filename(sym)
50
+ return {} unless File.exist?(filename)
51
+
52
+ YAML.load_file(filename).with_indifferent_access
53
+ end
49
54
 
50
- def self.load(sym)
51
- yaml_result = load_yaml_file(sym)
52
- EnvFallbackConfigLoader.new(yaml_result, sym, yaml_filename(sym))
55
+ def yaml_filename(sym)
56
+ file = "#{sym}.yml"
57
+ File.expand_path("~/.#{file}")
58
+ end
53
59
  end
54
60
  end
55
61
  end
@@ -22,8 +22,6 @@ module Checkoff
22
22
  LONG_CACHE_TIME = MINUTE * 15
23
23
  SHORT_CACHE_TIME = MINUTE * 5
24
24
 
25
- # XXX: Move low-level functions private
26
-
27
25
  def initialize(config: Checkoff::ConfigLoader.load(:asana),
28
26
  asana_client: Asana::Client,
29
27
  workspaces: Checkoff::Workspaces.new(config: config))
@@ -32,30 +30,23 @@ module Checkoff
32
30
  @workspaces = workspaces
33
31
  end
34
32
 
33
+ # Returns Asana Ruby API Client object
35
34
  def client
36
35
  @workspaces.client
37
36
  end
38
37
 
39
- def projects
40
- client.projects
41
- end
42
- cache_method :projects, LONG_CACHE_TIME
43
-
44
- def projects_by_workspace_name(workspace_name)
45
- workspace = @workspaces.workspace_by_name(workspace_name)
46
- raise "Could not find workspace named #{workspace_name}" unless workspace
47
-
48
- projects.find_by_workspace(workspace: workspace.gid)
49
- end
50
-
51
- def my_tasks(workspace_name)
52
- workspace = @workspaces.workspace_by_name(workspace_name)
53
- result = client.user_task_lists.get_user_task_list_for_user(user_gid: 'me',
54
- workspace: workspace.gid)
55
- gid = result.gid
56
- projects.find_by_id(gid)
38
+ # Default options used in Asana API to pull taskso
39
+ def task_options
40
+ {
41
+ per_page: 100,
42
+ options: {
43
+ fields: %w[name completed_at due_at due_on tags
44
+ memberships.project.gid memberships.section.name dependencies],
45
+ },
46
+ }
57
47
  end
58
48
 
49
+ # pulls an Asana API project class given a name
59
50
  def project(workspace_name, project_name)
60
51
  if project_name.is_a?(Symbol) && project_name.to_s.start_with?('my_tasks')
61
52
  my_tasks(workspace_name)
@@ -68,20 +59,12 @@ module Checkoff
68
59
  end
69
60
  cache_method :project, LONG_CACHE_TIME
70
61
 
62
+ # find uncompleted tasks in a list
71
63
  def active_tasks(tasks)
72
64
  tasks.select { |task| task.completed_at.nil? }
73
65
  end
74
66
 
75
- def task_options
76
- {
77
- per_page: 100,
78
- options: {
79
- fields: %w[name completed_at due_at due_on assignee_status tags
80
- memberships.project.gid memberships.section.name dependencies],
81
- },
82
- }
83
- end
84
-
67
+ # pull task objects from a named project
85
68
  def tasks_from_project(project, only_uncompleted: true, extra_fields: [])
86
69
  options = task_options
87
70
  options[:completed_since] = '9999-12-01' if only_uncompleted
@@ -90,5 +73,27 @@ module Checkoff
90
73
  client.tasks.find_all(**options).to_a
91
74
  end
92
75
  cache_method :tasks_from_project, SHORT_CACHE_TIME
76
+
77
+ private
78
+
79
+ def projects
80
+ client.projects
81
+ end
82
+ cache_method :projects, LONG_CACHE_TIME
83
+
84
+ def projects_by_workspace_name(workspace_name)
85
+ workspace = @workspaces.workspace_by_name(workspace_name)
86
+ raise "Could not find workspace named #{workspace_name}" unless workspace
87
+
88
+ projects.find_by_workspace(workspace: workspace.gid)
89
+ end
90
+
91
+ def my_tasks(workspace_name)
92
+ workspace = @workspaces.workspace_by_name(workspace_name)
93
+ result = client.user_task_lists.get_user_task_list_for_user(user_gid: 'me',
94
+ workspace: workspace.gid)
95
+ gid = result.gid
96
+ projects.find_by_id(gid)
97
+ end
93
98
  end
94
99
  end
@@ -4,7 +4,6 @@ require 'forwardable'
4
4
 
5
5
  module Checkoff
6
6
  # Query different sections of Asana projects
7
- # rubocop:disable Metrics/ClassLength
8
7
  class Sections
9
8
  MINUTE = 60
10
9
  LONG_CACHE_TIME = MINUTE * 15
@@ -23,68 +22,75 @@ module Checkoff
23
22
  @time = time
24
23
  end
25
24
 
26
- def_delegators :@projects, :client
27
-
28
- def file_task_by_section(by_section, task, project_gid)
29
- membership = task.memberships.find { |m| m['project']['gid'] == project_gid }
30
- raise "Could not find task in project_gid #{project_gid}: #{task}" if membership.nil?
25
+ # Returns a list of Asana API section objects for a given project
26
+ def sections_or_raise(workspace_name, project_name)
27
+ project = project_or_raise(workspace_name, project_name)
28
+ client.sections.get_sections_for_project(project_gid: project.gid)
29
+ end
31
30
 
32
- current_section = membership['section']['name']
33
- current_section = nil if current_section == '(no section)'
34
- by_section[current_section] ||= []
35
- by_section[current_section] << task
31
+ # Given a workspace name and project name, then provide a Hash of
32
+ # tasks with section name -> task list of the uncompleted tasks
33
+ def tasks_by_section(workspace_name, project_name)
34
+ project = project_or_raise(workspace_name, project_name)
35
+ tasks_by_section_for_project(project)
36
36
  end
37
+ cache_method :tasks_by_section, SHORT_CACHE_TIME
37
38
 
38
- def by_section(tasks, project_gid)
39
- by_section = {}
40
- tasks.each do |task|
41
- file_task_by_section(by_section, task, project_gid)
42
- end
43
- by_section
39
+ # XXX: Rename to section_tasks
40
+ #
41
+ # Pulls task objects from a specified section
42
+ def tasks(workspace_name, project_name, section_name,
43
+ only_uncompleted: true,
44
+ extra_fields: [])
45
+ section = section_or_raise(workspace_name, project_name, section_name)
46
+ options = projects.task_options
47
+ # asana-0.10.3 gem doesn't support per_page - not sure if API
48
+ # itself does
49
+ options.delete(:per_page)
50
+ options[:options][:fields] += extra_fields
51
+ options[:completed_since] = '9999-12-01' if only_uncompleted
52
+ client.tasks.get_tasks_for_section(section_gid: section.gid,
53
+ **options).to_a
44
54
  end
45
- cache_method :by_section, LONG_CACHE_TIME
55
+ cache_method :tasks, SHORT_CACHE_TIME
46
56
 
47
- def legacy_tasks_by_section_for_project(project)
48
- raw_tasks = projects.tasks_from_project(project)
49
- active_tasks = projects.active_tasks(raw_tasks)
50
- legacy_by_section(active_tasks)
57
+ # Pulls just names of tasks from a given section.
58
+ def section_task_names(workspace_name, project_name, section_name)
59
+ tasks = tasks(workspace_name, project_name, section_name)
60
+ tasks.map(&:name)
51
61
  end
62
+ cache_method :section_task_names, SHORT_CACHE_TIME
63
+
64
+ private
52
65
 
66
+ # Given a project object, pull all tasks, then provide a Hash of
67
+ # tasks with section name -> task list of the uncompleted tasks
53
68
  def tasks_by_section_for_project(project)
54
69
  raw_tasks = projects.tasks_from_project(project)
55
70
  active_tasks = projects.active_tasks(raw_tasks)
56
71
  by_section(active_tasks, project.gid)
57
72
  end
58
73
 
59
- def legacy_file_task_by_section(current_section, by_section, task)
60
- if task.name =~ /:$/
61
- current_section = task.name
62
- by_section[current_section] = []
63
- else
64
- by_section[current_section] ||= []
65
- by_section[current_section] << task
66
- end
67
- [current_section, by_section]
68
- end
74
+ def_delegators :@projects, :client
69
75
 
70
- def legacy_by_section(tasks)
71
- current_section = nil
76
+ # Given a list of tasks, pull a Hash of tasks with section name -> task list
77
+ def by_section(tasks, project_gid)
72
78
  by_section = {}
73
79
  tasks.each do |task|
74
- current_section, by_section =
75
- legacy_file_task_by_section(current_section, by_section, task)
80
+ file_task_by_section(by_section, task, project_gid)
76
81
  end
77
82
  by_section
78
83
  end
84
+ cache_method :by_section, LONG_CACHE_TIME
79
85
 
80
- def legacy_tasks_by_section_for_project_and_assignee_status(project,
81
- assignee_status)
82
- raw_tasks = projects.tasks_from_project(project)
83
- by_assignee_status =
84
- projects.active_tasks(raw_tasks)
85
- .group_by(&:assignee_status)
86
- active_tasks = by_assignee_status[assignee_status]
87
- legacy_by_section(active_tasks)
86
+ def file_task_by_section(by_section, task, project_gid)
87
+ membership = task.memberships.find { |m| m['project']['gid'] == project_gid }
88
+ raise "Could not find task in project_gid #{project_gid}: #{task}" if membership.nil?
89
+
90
+ current_section = membership['section']['name']
91
+ current_section = nil if current_section == '(no section)'
92
+ by_section[current_section] ||= []
93
+ by_section[current_section] << task
88
94
  end
89
95
 
90
96
  def project_or_raise(workspace_name, project_name)
@@ -96,88 +102,20 @@ module Checkoff
96
102
  project
97
103
  end
98
104
 
99
- def verify_legacy_user_task_list!(workspace_name)
100
- return unless user_task_list_migrated_to_real_sections?(workspace_name)
101
-
102
- raise NotImplementedError, 'Section-based user task lists not yet supported'
103
- end
104
-
105
- ASSIGNEE_STATUS_BY_PROJECT_NAME = {
106
- my_tasks_new: 'inbox',
107
- my_tasks_today: 'today',
108
- my_tasks_upcoming: 'upcoming',
109
- }.freeze
110
-
111
- def user_task_list_by_section(workspace_name, project_name)
112
- verify_legacy_user_task_list!(workspace_name)
113
-
114
- project = projects.project(workspace_name, project_name)
115
- if project_name == :my_tasks
116
- legacy_tasks_by_section_for_project(project)
117
- else
118
- legacy_tasks_by_section_for_project_and_assignee_status(project,
119
- ASSIGNEE_STATUS_BY_PROJECT_NAME.fetch(project_name))
120
- end
121
- end
122
-
123
- def tasks_by_section(workspace_name, project_name)
124
- project = project_or_raise(workspace_name, project_name)
125
- case project_name
126
- when :my_tasks, :my_tasks_new, :my_tasks_today, :my_tasks_upcoming
127
- user_task_list_by_section(workspace_name, project_name)
128
- else
129
- tasks_by_section_for_project(project)
130
- end
131
- end
132
- cache_method :tasks_by_section, SHORT_CACHE_TIME
133
-
134
- # XXX: Rename to section_tasks
135
- def tasks(workspace_name, project_name, section_name)
136
- tasks_by_section(workspace_name, project_name).fetch(section_name, [])
137
- end
138
- cache_method :tasks, SHORT_CACHE_TIME
139
-
140
- def section_task_names(workspace_name, project_name, section_name)
141
- tasks = tasks(workspace_name, project_name, section_name)
142
- if tasks.nil?
143
- by_section = tasks_by_section(workspace_name, project_name)
144
- desc = "#{workspace_name} | #{project_name} | #{section_name}"
145
- raise "Could not find task names for #{desc}. " \
146
- "Valid sections: #{by_section.keys}"
147
- end
148
- tasks.map(&:name)
149
- end
150
- cache_method :section_task_names, SHORT_CACHE_TIME
151
-
152
- def user_task_list_migrated_to_real_sections?(workspace_name)
153
- workspace = workspaces.workspace_by_name(workspace_name)
154
- result = client.user_task_lists.get_user_task_list_for_user(user_gid: 'me',
155
- workspace: workspace.gid)
156
- result.migration_status != 'not_migrated'
105
+ def section(workspace_name, project_name, section_name)
106
+ sections = sections_or_raise(workspace_name, project_name)
107
+ sections.find { |section| section.name.chomp(':') == section_name.chomp(':') }
157
108
  end
158
109
 
159
- def task_due?(task)
160
- if task.due_at
161
- Time.parse(task.due_at) <= time.now
162
- elsif task.due_on
163
- Date.parse(task.due_on) <= time.today
164
- else
165
- true # set a due date if you don't want to do this now
166
- end
167
- end
110
+ def section_or_raise(workspace_name, project_name, section_name)
111
+ section = section(workspace_name, project_name, section_name)
112
+ if section.nil?
113
+ valid_sections = sections_or_raise(workspace_name, project_name).map(&:name)
168
114
 
169
- def project_task_names(workspace_name, project_name)
170
- by_section = tasks_by_section(workspace_name, project_name)
171
- by_section.flat_map do |section_name, tasks|
172
- task_names = tasks.map(&:name)
173
- if section_name.nil?
174
- task_names
175
- else
176
- [section_name, task_names]
177
- end
115
+ raise "Could not find section #{section_name} under project #{project_name} " \
116
+ "under workspace #{workspace_name}. Valid sections: #{valid_sections}"
178
117
  end
118
+ section
179
119
  end
180
- cache_method :project_task_names, SHORT_CACHE_TIME
181
120
  end
182
- # rubocop:enable Metrics/ClassLength
183
121
  end
@@ -11,17 +11,7 @@ module Checkoff
11
11
 
12
12
  extend Forwardable
13
13
 
14
- def file_task_by_section(current_section, by_section, task)
15
- if task.name =~ /:$/
16
- current_section = task.name
17
- by_section[current_section] = []
18
- else
19
- by_section[current_section] ||= []
20
- by_section[current_section] << task
21
- end
22
- [current_section, by_section]
23
- end
24
-
14
+ # pulls a Hash of subtasks broken out by section
25
15
  def by_section(tasks)
26
16
  current_section = nil
27
17
  by_section = {}
@@ -37,5 +27,18 @@ module Checkoff
37
27
  task.subtasks(projects.task_options)
38
28
  end
39
29
  cache_method :raw_subtasks, LONG_CACHE_TIME
30
+
31
+ private
32
+
33
+ def file_task_by_section(current_section, by_section, task)
34
+ if task.name =~ /:$/
35
+ current_section = task.name
36
+ by_section[current_section] = []
37
+ else
38
+ by_section[current_section] ||= []
39
+ by_section[current_section] << task
40
+ end
41
+ [current_section, by_section]
42
+ end
40
43
  end
41
44
  end
@@ -5,7 +5,7 @@
5
5
  require_relative 'sections'
6
6
 
7
7
  module Checkoff
8
- # Pull things from 'my tasks' in Asana
8
+ # Pull tasks from Asana
9
9
  class Tasks
10
10
  MINUTE = 60
11
11
  HOUR = MINUTE * 60
@@ -22,25 +22,21 @@ module Checkoff
22
22
  @asana_task = asana_task
23
23
  end
24
24
 
25
- def client
26
- @sections.client
27
- end
28
-
29
- def projects
30
- @projects ||= @sections.projects
31
- end
32
-
33
- def task(workspace_name, project_name, task_name, only_uncompleted: true)
25
+ # Pull a specific task by name
26
+ def task(workspace_name, project_name, task_name,
27
+ section_name: :unspecified,
28
+ only_uncompleted: true)
34
29
  project = projects.project(workspace_name, project_name)
35
- tasks = projects.tasks_from_project(project,
36
- only_uncompleted: only_uncompleted)
30
+ tasks = if section_name == :unspecified
31
+ projects.tasks_from_project(project,
32
+ only_uncompleted: only_uncompleted)
33
+ else
34
+ @sections.tasks(workspace_name, project_name, section_name,
35
+ only_uncompleted: only_uncompleted)
36
+ end
37
37
  tasks.find { |task| task.name == task_name }
38
38
  end
39
39
 
40
- def tasks_minus_sections(tasks)
41
- @sections.by_section(tasks).values.flatten
42
- end
43
-
44
40
  def add_task(name,
45
41
  workspace_gid: default_workspace_gid,
46
42
  assignee_gid: default_assignee_gid)
@@ -49,6 +45,16 @@ module Checkoff
49
45
  workspace: workspace_gid, name: name)
50
46
  end
51
47
 
48
+ private
49
+
50
+ def client
51
+ @sections.client
52
+ end
53
+
54
+ def projects
55
+ @projects ||= @sections.projects
56
+ end
57
+
52
58
  def default_assignee_gid
53
59
  @config.fetch(:default_assignee_gid)
54
60
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Checkoff
4
- VERSION = '0.10.0'
4
+ # Version of library
5
+ VERSION = '0.12.1'
5
6
  end
@@ -9,6 +9,7 @@ module Checkoff
9
9
  @asana_client = asana_client
10
10
  end
11
11
 
12
+ # Returns Asana Ruby API Client object
12
13
  def client
13
14
  @client ||= @asana_client.new do |c|
14
15
  c.authentication :access_token, @config.fetch(:personal_access_token)
@@ -17,14 +18,17 @@ module Checkoff
17
18
  end
18
19
  end
19
20
 
20
- def default_workspace_gid
21
- @config.fetch(:default_workspace_gid)
22
- end
23
-
21
+ # Pulls an Asana workspace object
24
22
  def workspace_by_name(workspace_name)
25
23
  client.workspaces.find_all.find do |workspace|
26
24
  workspace.name == workspace_name
27
25
  end || raise("Could not find workspace named [#{workspace_name}]")
28
26
  end
27
+
28
+ private
29
+
30
+ def default_workspace_gid
31
+ @config.fetch(:default_workspace_gid)
32
+ end
29
33
  end
30
34
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  desc 'Run tasks to be done during a continuous integration (CI) build'
4
- task ci: :localtest
4
+ task citest: %i[clear_metrics test undercover]
@@ -1,8 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- desc 'Rebaseline quality thresholds to last commit'
3
+ desc 'Ensure that any locally ratcheted coverage metrics are cleared back ' \
4
+ 'to git baseline'
4
5
  task :clear_metrics do |_t|
5
6
  ret =
6
7
  system('git checkout coverage/.last_run.json')
7
8
  raise unless ret
9
+
10
+ # Without this old lines which are removed are still counted,
11
+ # leading to inconsistent coverage percentages between runs.
12
+ #
13
+ # need to save coverage/.last_run.json
14
+ ret =
15
+ system('rm -fr coverage/assets coverage/.*.json.lock coverage/lcov/* coverage/index.html coverage/.resultset.json')
16
+ raise unless ret
8
17
  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.10.0
4
+ version: 0.12.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: 2021-05-11 00:00:00.000000000 Z
11
+ date: 2021-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: gli
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: bump
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +142,14 @@ dependencies:
128
142
  requirements:
129
143
  - - ">="
130
144
  - !ruby/object:Gem::Version
131
- version: '0'
145
+ version: 0.58.0
132
146
  type: :development
133
147
  prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
150
  - - ">="
137
151
  - !ruby/object:Gem::Version
138
- version: '0'
152
+ version: 0.58.0
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: rake
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +220,20 @@ dependencies:
206
220
  - - ">="
207
221
  - !ruby/object:Gem::Version
208
222
  version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: simplecov-lcov
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
209
237
  - !ruby/object:Gem::Dependency
210
238
  name: undercover
211
239
  requirement: !ruby/object:Gem::Requirement
@@ -281,7 +309,7 @@ files:
281
309
  - metrics/rubocop_high_water_mark
282
310
  - metrics/scalastyle_high_water_mark
283
311
  - metrics/shellcheck_high_water_mark
284
- - rakelib/ci.rake
312
+ - rakelib/citest.rake
285
313
  - rakelib/clear_metrics.rake
286
314
  - rakelib/default.rake
287
315
  - rakelib/gem_tasks.rake
@@ -310,7 +338,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
338
  - !ruby/object:Gem::Version
311
339
  version: '0'
312
340
  requirements: []
313
- rubygems_version: 3.1.2
341
+ rubygems_version: 3.0.3.1
314
342
  signing_key:
315
343
  specification_version: 4
316
344
  summary: Command-line and gem client for Asana (unofficial)