checkoff 0.9.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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