checkoff 0.9.0 → 0.12.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.
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
@@ -17,23 +17,42 @@ help:
17
17
 
18
18
  default: localtest ## run default typechecking and tests
19
19
 
20
- clean: ## remove all built artifacts
20
+ requirements_dev.txt.installed: requirements_dev.txt
21
+ pip install -q --disable-pip-version-check -r requirements_dev.txt
22
+ touch requirements_dev.txt.installed
21
23
 
22
- test: spec ## run tests quickly
24
+ pip_install: requirements_dev.txt.installed ## Install Python dependencies
23
25
 
24
- quality: ## run precommit quality checks
25
- @bundle exec overcommit --run
26
+ Gemfile.lock:
27
+ bundle install
28
+
29
+ Gemfile.lock.installed: Gemfile.lock
30
+ bundle install
31
+ touch Gemfile.lock.installed
32
+
33
+ bundle_install: Gemfile.lock.installed ## Install Ruby dependencies
34
+
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
39
+
40
+ quality: ## run precommit quality checks
41
+ bundle exec overcommit --run
26
42
 
27
43
  test: ## Run lower-level tests
28
44
  @bundle exec rake test
29
45
 
30
- localtest: test quality feature ## run default local actions
46
+ localtest: ## run default local actions
47
+ @bundle exec rake localtest
31
48
 
32
49
  update_from_cookiecutter: ## Bring in changes from template project used to create this repo
50
+ bundle exec overcommit --uninstall
33
51
  IN_COOKIECUTTER_PROJECT_UPGRADER=1 cookiecutter_project_upgrader || true
34
- git checkout cookiecutter-template && git push && (git checkout main; bundle exec overcommit --sign)
35
- git checkout main && bundle exec overcommit --sign && git pull && (git checkout -b update-from-cookiecutter-$$(date +%Y-%m-%d-%H%M); bundle exec overcommit --sign)
52
+ git checkout cookiecutter-template && git push && git checkout main
53
+ git checkout main && git pull && git checkout -b update-from-cookiecutter-$$(date +%Y-%m-%d-%H%M)
36
54
  git merge cookiecutter-template || true
55
+ bundle exec overcommit --install
37
56
  @echo
38
57
  @echo "Please resolve any merge conflicts below and push up a PR with:"
39
58
  @echo
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/Rakefile CHANGED
@@ -1,11 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # bundle exec rake release to release a new gem
5
- #
6
- require 'bundler/gem_tasks'
7
-
8
- Dir['lib/tasks/**/*.rake'].each { |t| load t }
9
-
10
- desc 'Default: Run specs and check quality.'
11
- task default: [:localtest]
3
+ # Add your own tasks in files placed in rakelib/ ending in .rake,
4
+ # for example rakelib/capistrano.rake, and they will automatically be available to Rake.
data/bin/overcommit ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'overcommit' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("overcommit", "overcommit")
data/checkoff.gemspec CHANGED
@@ -12,8 +12,8 @@ Gem::Specification.new do |spec|
12
12
  spec.email = ['vince@broz.cc']
13
13
  spec.summary = 'Command-line and gem client for Asana (unofficial)'
14
14
  spec.homepage = 'https://github.com/apiology/checkoff'
15
- spec.license = 'MIT'
16
- spec.required_ruby_version = '>= 2.5'
15
+ spec.license = 'MIT license'
16
+ spec.required_ruby_version = '>= 2.6'
17
17
 
18
18
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
19
  f.match(%r{^(test|spec|features)/})
@@ -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
- spec.add_development_dependency 'rspec', '>=3.4'
37
40
  spec.add_development_dependency 'rubocop'
38
41
  spec.add_development_dependency 'rubocop-minitest'
39
42
  spec.add_development_dependency 'rubocop-rake'
40
43
  spec.add_development_dependency 'simplecov'
44
+ spec.add_development_dependency 'simplecov-lcov'
45
+ spec.add_development_dependency 'undercover'
41
46
  end
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "result": {
3
- "covered_percent": 89.61
3
+ "line": 97.0,
4
+ "branch": 80.95
4
5
  }
5
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,8 +123,18 @@ 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
105
- bundle install
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
137
+ make bundle_install
106
138
  # https://bundler.io/v2.0/bundle_lock.html#SUPPORTING-OTHER-PLATFORMS
107
139
  #
108
140
  # "If you want your bundle to support platforms other than the one
@@ -111,7 +143,7 @@ ensure_bundle() {
111
143
  # re-resolve and consider the new platform when picking gems, all
112
144
  # without needing to have a machine that matches PLATFORM handy to
113
145
  # install those platform-specific gems on.'
114
- bundle lock --add-platform x86_64-darwin-20 x86_64-linux
146
+ grep x86_64-darwin-20 Gemfile.lock >/dev/null 2>&1 || bundle lock --add-platform x86_64-darwin-20 x86_64-linux
115
147
  }
116
148
 
117
149
  set_ruby_local_version() {
@@ -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
@@ -239,11 +274,15 @@ ensure_pyenv_virtualenvs() {
239
274
  ensure_pip() {
240
275
  # Make sure we have a pip with the 20.3 resolver, and after the
241
276
  # initial bugfix release
242
- pip install 'pip>=20.3.1'
277
+ major_pip_version=$(pip --version | cut -d' ' -f2 | cut -d '.' -f 1)
278
+ if [[ major_pip_version -lt 21 ]]
279
+ then
280
+ pip install 'pip>=20.3.1'
281
+ fi
243
282
  }
244
283
 
245
284
  ensure_python_requirements() {
246
- pip install -r requirements_dev.txt
285
+ make pip_install
247
286
  }
248
287
 
249
288
  ensure_shellcheck() {
@@ -253,12 +292,31 @@ ensure_shellcheck() {
253
292
  fi
254
293
  }
255
294
 
295
+ ensure_overcommit() {
296
+ # don't run if we're in the middle of a cookiecutter child project
297
+ # test, or otherwise don't have a Git repo to install hooks into...
298
+ if [ -d .git ]
299
+ then
300
+ bundle exec overcommit --install
301
+ else
302
+ >&2 echo 'Not in a git repo; not installing git hooks'
303
+ fi
304
+ }
305
+
306
+ ensure_rugged_packages_installed() {
307
+ install_package icu4c libicu-dev # needed by rugged, needed by undercover
308
+ install_package pkg-config # needed by rugged, needed by undercover
309
+ install_package cmake # needed by rugged, needed by undercover
310
+ }
311
+
256
312
  ensure_rbenv
257
313
 
258
314
  ensure_ruby_versions
259
315
 
260
316
  set_ruby_local_version
261
317
 
318
+ ensure_rugged_packages_installed
319
+
262
320
  ensure_bundle
263
321
 
264
322
  ensure_pyenv
@@ -272,3 +330,5 @@ ensure_pip
272
330
  ensure_python_requirements
273
331
 
274
332
  ensure_shellcheck
333
+
334
+ ensure_overcommit
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..-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