checkoff 0.7.0 → 0.11.1

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +120 -12
  3. data/.envrc +2 -0
  4. data/.git-hooks/pre_commit/circle_ci.rb +21 -0
  5. data/.gitignore +51 -1
  6. data/.overcommit.yml +32 -12
  7. data/.rubocop.yml +64 -168
  8. data/.yamllint.yml +8 -0
  9. data/CODE_OF_CONDUCT.md +120 -36
  10. data/DEVELOPMENT.md +28 -0
  11. data/Gemfile.lock +139 -0
  12. data/{LICENSE.txt → LICENSE} +7 -6
  13. data/Makefile +52 -18
  14. data/README.md +13 -2
  15. data/Rakefile +2 -9
  16. data/bin/bump +29 -0
  17. data/bin/checkoff +29 -0
  18. data/bin/overcommit +29 -0
  19. data/bin/rake +29 -0
  20. data/checkoff.gemspec +14 -6
  21. data/coverage/.last_run.json +2 -1
  22. data/docs/cookiecutter_input.json +13 -0
  23. data/exe/checkoff +1 -2
  24. data/fix.sh +334 -0
  25. data/lib/checkoff/cli.rb +97 -76
  26. data/lib/checkoff/config_loader.rb +22 -16
  27. data/lib/checkoff/projects.rb +35 -31
  28. data/lib/checkoff/sections.rb +98 -50
  29. data/lib/checkoff/subtasks.rb +14 -11
  30. data/lib/checkoff/tasks.rb +25 -16
  31. data/lib/checkoff/version.rb +2 -1
  32. data/lib/checkoff/workspaces.rb +10 -5
  33. data/metrics/bigfiles_high_water_mark +1 -1
  34. data/metrics/rubocop_high_water_mark +1 -1
  35. data/{lib/tasks/ci.rake → rakelib/citest.rake} +1 -1
  36. data/rakelib/clear_metrics.rake +17 -0
  37. data/{lib/tasks → rakelib}/default.rake +0 -0
  38. data/rakelib/gem_tasks.rake +3 -0
  39. data/{lib/tasks → rakelib}/localtest.rake +1 -1
  40. data/rakelib/overcommit.rake +6 -0
  41. data/rakelib/quality.rake +4 -0
  42. data/{lib/tasks → rakelib}/test.rake +0 -0
  43. data/rakelib/undercover.rake +8 -0
  44. data/requirements_dev.txt +2 -0
  45. metadata +110 -29
  46. data/.ruby-version +0 -1
  47. data/.travis.yml +0 -22
  48. data/lib/tasks/clear_metrics.rake +0 -8
  49. data/lib/tasks/feature.rake +0 -9
  50. data/lib/tasks/quality.rake +0 -9
  51. data/lib/tasks/spec.rake +0 -9
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,40 +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
- class CLI
16
- attr_reader :sections, :stderr
17
-
18
- def initialize(config: Checkoff::ConfigLoader.load(:asana),
19
- 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),
20
20
  projects: Checkoff::Projects.new(config: config),
21
21
  sections: Checkoff::Sections.new(config: config,
22
22
  projects: projects),
23
- tasks: Checkoff::Tasks.new(config: config),
24
- stderr: $stderr,
25
- kernel: Kernel)
26
- @workspaces = workspaces
27
- @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
28
31
  @sections = sections
29
32
  @tasks = tasks
30
- @kernel = kernel
31
- @stderr = stderr
32
33
  end
33
34
 
34
- def task_to_hash(task)
35
- task_out = {
36
- name: task.name,
37
- }
38
- if task.due_on
39
- task_out[:due] = task.due_on
40
- elsif task.due_at
41
- 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)
42
42
  end
43
- task_out
44
43
  end
45
44
 
46
- def tasks_to_hash(tasks)
47
- 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
48
53
  end
49
54
 
50
55
  def run_on_project(workspace, project)
@@ -62,72 +67,88 @@ module Checkoff
62
67
  tasks_to_hash(tasks).to_json
63
68
  end
64
69
 
65
- def quickadd(workspace_name, task_name)
66
- workspace = @workspaces.workspace_by_name(workspace_name)
67
- @tasks.add_task(task_name,
68
- 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
69
74
  end
70
75
 
71
- def validate_args!(args)
72
- return unless args.length < 2 || !%w[view quickadd].include?(args[0])
73
-
74
- output_help
75
- 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
76
86
  end
77
87
 
78
- def parse_view_args(subargs, args)
79
- subargs.workspace = args[1]
80
- subargs.project = args[2]
81
- subargs.section = args[3]
88
+ def tasks_to_hash(tasks)
89
+ tasks.map { |task| task_to_hash(task) }
82
90
  end
83
91
 
84
- def parse_quickadd_args(subargs, args)
85
- subargs.workspace = args[1]
86
- subargs.task_name = args[2]
87
- end
92
+ attr_reader :workspace_name, :project_name, :section_name, :task_name, :sections, :tasks, :stderr
93
+ end
88
94
 
89
- def parse_args(args)
90
- mode = args[0]
91
- subargs = OpenStruct.new
92
- case mode
93
- when 'view'
94
- parse_view_args(subargs, args)
95
- when 'quickadd'
96
- parse_quickadd_args(subargs, args)
97
- else
98
- raise
99
- end
100
- [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
101
105
  end
102
106
 
103
- def output_help
104
- stderr.puts 'View tasks:'
105
- stderr.puts " #{$PROGRAM_NAME} view workspace project [section]"
106
- stderr.puts " #{$PROGRAM_NAME} quickadd workspace task_name"
107
- stderr.puts
108
- stderr.puts "'project' can be set to a project name, or :my_tasks, " \
109
- ":my_tasks_upcoming, :my_tasks_new, or :my_tasks_today"
107
+ def run
108
+ workspace = @workspaces.workspace_by_name(workspace_name)
109
+ @tasks.add_task(task_name,
110
+ workspace_gid: workspace.gid)
110
111
  end
111
112
 
112
- def view(workspace_name, project_name, section_name)
113
- project_name = project_name[1..-1].to_sym if project_name.start_with? ':'
114
- if section_name.nil?
115
- run_on_project(workspace_name, project_name)
116
- else
117
- 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
118
136
  end
119
137
  end
120
138
 
121
- def run(args)
122
- validate_args!(args)
123
- command, subargs = parse_args(args)
124
- case command
125
- when 'view'
126
- view(subargs.workspace, subargs.project, subargs.section)
127
- when 'quickadd'
128
- quickadd(subargs.workspace, subargs.task_name)
129
- else
130
- 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
131
152
  end
132
153
  end
133
154
  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,12 @@ 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
- my_tasks = @config.fetch(:my_tasks)
53
- gid = my_tasks[workspace_name] unless my_tasks.nil?
54
- raise "Please define [:my_tasks][#{workspace_name}] in config file" if my_tasks.nil? || gid.nil?
55
-
56
- projects.find_by_id(gid)
57
- end
58
-
38
+ # pulls an Asana API project class given a name
59
39
  def project(workspace_name, project_name)
60
40
  if project_name.is_a?(Symbol) && project_name.to_s.start_with?('my_tasks')
61
41
  my_tasks(workspace_name)
@@ -68,10 +48,43 @@ module Checkoff
68
48
  end
69
49
  cache_method :project, LONG_CACHE_TIME
70
50
 
51
+ # find uncompleted tasks in a list
71
52
  def active_tasks(tasks)
72
53
  tasks.select { |task| task.completed_at.nil? }
73
54
  end
74
55
 
56
+ # pull task objects from a named project
57
+ def tasks_from_project(project, only_uncompleted: true, extra_fields: [])
58
+ options = task_options
59
+ options[:completed_since] = '9999-12-01' if only_uncompleted
60
+ options[:project] = project.gid
61
+ options[:options][:fields] += extra_fields
62
+ client.tasks.find_all(**options).to_a
63
+ end
64
+ cache_method :tasks_from_project, SHORT_CACHE_TIME
65
+
66
+ private
67
+
68
+ def projects
69
+ client.projects
70
+ end
71
+ cache_method :projects, LONG_CACHE_TIME
72
+
73
+ def projects_by_workspace_name(workspace_name)
74
+ workspace = @workspaces.workspace_by_name(workspace_name)
75
+ raise "Could not find workspace named #{workspace_name}" unless workspace
76
+
77
+ projects.find_by_workspace(workspace: workspace.gid)
78
+ end
79
+
80
+ def my_tasks(workspace_name)
81
+ workspace = @workspaces.workspace_by_name(workspace_name)
82
+ result = client.user_task_lists.get_user_task_list_for_user(user_gid: 'me',
83
+ workspace: workspace.gid)
84
+ gid = result.gid
85
+ projects.find_by_id(gid)
86
+ end
87
+
75
88
  def task_options
76
89
  {
77
90
  per_page: 100,
@@ -81,14 +94,5 @@ module Checkoff
81
94
  },
82
95
  }
83
96
  end
84
-
85
- def tasks_from_project(project, only_uncompleted: true, extra_fields: [])
86
- options = task_options
87
- options[:completed_since] = '9999-12-01' if only_uncompleted
88
- options[:project] = project.gid
89
- options[:options][:fields] += extra_fields
90
- client.tasks.find_all(**options).to_a
91
- end
92
- cache_method :tasks_from_project, SHORT_CACHE_TIME
93
97
  end
94
98
  end
@@ -4,6 +4,7 @@ require 'forwardable'
4
4
 
5
5
  module Checkoff
6
6
  # Query different sections of Asana projects
7
+ # rubocop:disable Metrics/ClassLength
7
8
  class Sections
8
9
  MINUTE = 60
9
10
  LONG_CACHE_TIME = MINUTE * 15
@@ -11,15 +12,82 @@ module Checkoff
11
12
 
12
13
  extend Forwardable
13
14
 
14
- attr_reader :projects, :time
15
+ attr_reader :projects, :workspaces, :time
15
16
 
16
17
  def initialize(config: Checkoff::ConfigLoader.load(:asana),
17
18
  projects: Checkoff::Projects.new(config: config),
19
+ workspaces: Checkoff::Workspaces.new(config: config),
18
20
  time: Time)
19
21
  @projects = projects
22
+ @workspaces = workspaces
20
23
  @time = time
21
24
  end
22
25
 
26
+ # Given a list of tasks, pull a Hash of tasks with section name -> task list
27
+ def by_section(tasks, project_gid)
28
+ by_section = {}
29
+ tasks.each do |task|
30
+ file_task_by_section(by_section, task, project_gid)
31
+ end
32
+ by_section
33
+ end
34
+ cache_method :by_section, LONG_CACHE_TIME
35
+
36
+ # Given a project object, pull all tasks, then provide a Hash of
37
+ # tasks with section name -> task list of the uncompleted tasks
38
+ def tasks_by_section_for_project(project)
39
+ raw_tasks = projects.tasks_from_project(project)
40
+ active_tasks = projects.active_tasks(raw_tasks)
41
+ by_section(active_tasks, project.gid)
42
+ end
43
+
44
+ # Given a workspace name and project name, then provide a Hash of
45
+ # tasks with section name -> task list of the uncompleted tasks
46
+ def tasks_by_section(workspace_name, project_name)
47
+ project = project_or_raise(workspace_name, project_name)
48
+ case project_name
49
+ when :my_tasks, :my_tasks_new, :my_tasks_today, :my_tasks_upcoming
50
+ user_task_list_by_section(workspace_name, project_name)
51
+ else
52
+ tasks_by_section_for_project(project)
53
+ end
54
+ end
55
+ cache_method :tasks_by_section, SHORT_CACHE_TIME
56
+
57
+ # XXX: Rename to section_tasks
58
+ #
59
+ # Pulls task objects from a specified section
60
+ def tasks(workspace_name, project_name, section_name)
61
+ tasks_by_section(workspace_name, project_name).fetch(section_name, [])
62
+ end
63
+ cache_method :tasks, SHORT_CACHE_TIME
64
+
65
+ # Pulls just names of tasks from a given section.
66
+ def section_task_names(workspace_name, project_name, section_name)
67
+ tasks = tasks(workspace_name, project_name, section_name)
68
+ if tasks.nil?
69
+ by_section = tasks_by_section(workspace_name, project_name)
70
+ desc = "#{workspace_name} | #{project_name} | #{section_name}"
71
+ raise "Could not find task names for #{desc}. " \
72
+ "Valid sections: #{by_section.keys}"
73
+ end
74
+ tasks.map(&:name)
75
+ end
76
+ cache_method :section_task_names, SHORT_CACHE_TIME
77
+
78
+ # returns if a task's due field is at or before the current date/time
79
+ def task_due?(task)
80
+ if task.due_at
81
+ Time.parse(task.due_at) <= time.now
82
+ elsif task.due_on
83
+ Date.parse(task.due_on) <= time.today
84
+ else
85
+ true # set a due date if you don't want to do this now
86
+ end
87
+ end
88
+
89
+ private
90
+
23
91
  def_delegators :@projects, :client
24
92
 
25
93
  def file_task_by_section(by_section, task, project_gid)
@@ -32,19 +100,10 @@ module Checkoff
32
100
  by_section[current_section] << task
33
101
  end
34
102
 
35
- def by_section(tasks, project_gid)
36
- by_section = {}
37
- tasks.each do |task|
38
- file_task_by_section(by_section, task, project_gid)
39
- end
40
- by_section
41
- end
42
- cache_method :by_section, LONG_CACHE_TIME
43
-
44
- def tasks_by_section_for_project(project)
103
+ def legacy_tasks_by_section_for_project(project)
45
104
  raw_tasks = projects.tasks_from_project(project)
46
105
  active_tasks = projects.active_tasks(raw_tasks)
47
- by_section(active_tasks, project.gid)
106
+ legacy_by_section(active_tasks)
48
107
  end
49
108
 
50
109
  def legacy_file_task_by_section(current_section, by_section, task)
@@ -68,12 +127,12 @@ module Checkoff
68
127
  by_section
69
128
  end
70
129
 
71
- def tasks_by_section_for_project_and_assignee_status(project,
72
- assignee_status)
130
+ def legacy_tasks_by_section_for_project_and_assignee_status(project,
131
+ assignee_status)
73
132
  raw_tasks = projects.tasks_from_project(project)
74
133
  by_assignee_status =
75
134
  projects.active_tasks(raw_tasks)
76
- .group_by(&:assignee_status)
135
+ .group_by(&:assignee_status)
77
136
  active_tasks = by_assignee_status[assignee_status]
78
137
  legacy_by_section(active_tasks)
79
138
  end
@@ -87,49 +146,37 @@ module Checkoff
87
146
  project
88
147
  end
89
148
 
90
- def tasks_by_section(workspace_name, project_name)
91
- project = project_or_raise(workspace_name, project_name)
92
- case project_name
93
- when :my_tasks_new
94
- tasks_by_section_for_project_and_assignee_status(project, 'inbox')
95
- when :my_tasks_today
96
- tasks_by_section_for_project_and_assignee_status(project, 'today')
97
- when :my_tasks_upcoming
98
- tasks_by_section_for_project_and_assignee_status(project, 'upcoming')
99
- else
100
- tasks_by_section_for_project(project)
101
- end
102
- end
103
- cache_method :tasks_by_section, SHORT_CACHE_TIME
149
+ def verify_legacy_user_task_list!(workspace_name)
150
+ return unless user_task_list_migrated_to_real_sections?(workspace_name)
104
151
 
105
- # XXX: Rename to section_tasks
106
- def tasks(workspace_name, project_name, section_name)
107
- tasks_by_section(workspace_name, project_name)[section_name]
152
+ raise NotImplementedError, 'Section-based user task lists not yet supported'
108
153
  end
109
- cache_method :tasks, SHORT_CACHE_TIME
110
154
 
111
- def section_task_names(workspace_name, project_name, section_name)
112
- tasks = tasks(workspace_name, project_name, section_name)
113
- if tasks.nil?
114
- by_section = tasks_by_section(workspace_name, project_name)
115
- desc = "#{workspace_name} | #{project_name} | #{section_name}"
116
- raise "Could not find task names for #{desc}. " \
117
- "Valid sections: #{by_section.keys}"
118
- end
119
- tasks.map(&:name)
120
- end
121
- cache_method :section_task_names, SHORT_CACHE_TIME
155
+ ASSIGNEE_STATUS_BY_PROJECT_NAME = {
156
+ my_tasks_new: 'inbox',
157
+ my_tasks_today: 'today',
158
+ my_tasks_upcoming: 'upcoming',
159
+ }.freeze
122
160
 
123
- def task_due?(task)
124
- if task.due_at
125
- Time.parse(task.due_at) <= time.now
126
- elsif task.due_on
127
- Date.parse(task.due_on) <= time.today
161
+ def user_task_list_by_section(workspace_name, project_name)
162
+ verify_legacy_user_task_list!(workspace_name)
163
+
164
+ project = projects.project(workspace_name, project_name)
165
+ if project_name == :my_tasks
166
+ legacy_tasks_by_section_for_project(project)
128
167
  else
129
- true # set a due date if you don't want to do this now
168
+ legacy_tasks_by_section_for_project_and_assignee_status(project,
169
+ ASSIGNEE_STATUS_BY_PROJECT_NAME.fetch(project_name))
130
170
  end
131
171
  end
132
172
 
173
+ def user_task_list_migrated_to_real_sections?(workspace_name)
174
+ workspace = workspaces.workspace_by_name(workspace_name)
175
+ result = client.user_task_lists.get_user_task_list_for_user(user_gid: 'me',
176
+ workspace: workspace.gid)
177
+ result.migration_status != 'not_migrated'
178
+ end
179
+
133
180
  def project_task_names(workspace_name, project_name)
134
181
  by_section = tasks_by_section(workspace_name, project_name)
135
182
  by_section.flat_map do |section_name, tasks|
@@ -143,4 +190,5 @@ module Checkoff
143
190
  end
144
191
  cache_method :project_task_names, SHORT_CACHE_TIME
145
192
  end
193
+ # rubocop:enable Metrics/ClassLength
146
194
  end