abt-cli 0.0.2

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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/bin/abt +14 -0
  3. data/lib/abt.rb +5 -0
  4. data/lib/abt/asana_client.rb +53 -0
  5. data/lib/abt/cli.rb +103 -0
  6. data/lib/abt/cli/dialogs.rb +67 -0
  7. data/lib/abt/git_config.rb +78 -0
  8. data/lib/abt/harvest_client.rb +58 -0
  9. data/lib/abt/help.rb +56 -0
  10. data/lib/abt/help/cli.rb +59 -0
  11. data/lib/abt/help/markdown.rb +66 -0
  12. data/lib/abt/http_error.rb +45 -0
  13. data/lib/abt/providers.rb +5 -0
  14. data/lib/abt/providers/asana.rb +60 -0
  15. data/lib/abt/providers/asana/base_command.rb +62 -0
  16. data/lib/abt/providers/asana/clear.rb +24 -0
  17. data/lib/abt/providers/asana/clear_global.rb +24 -0
  18. data/lib/abt/providers/asana/current.rb +69 -0
  19. data/lib/abt/providers/asana/harvest_link_entry_data.rb +48 -0
  20. data/lib/abt/providers/asana/init.rb +62 -0
  21. data/lib/abt/providers/asana/move.rb +54 -0
  22. data/lib/abt/providers/asana/pick_task.rb +46 -0
  23. data/lib/abt/providers/asana/projects.rb +30 -0
  24. data/lib/abt/providers/asana/start.rb +22 -0
  25. data/lib/abt/providers/asana/tasks.rb +35 -0
  26. data/lib/abt/providers/harvest.rb +52 -0
  27. data/lib/abt/providers/harvest/base_command.rb +70 -0
  28. data/lib/abt/providers/harvest/clear.rb +24 -0
  29. data/lib/abt/providers/harvest/clear_global.rb +24 -0
  30. data/lib/abt/providers/harvest/current.rb +79 -0
  31. data/lib/abt/providers/harvest/init.rb +61 -0
  32. data/lib/abt/providers/harvest/pick_task.rb +45 -0
  33. data/lib/abt/providers/harvest/projects.rb +29 -0
  34. data/lib/abt/providers/harvest/start.rb +58 -0
  35. data/lib/abt/providers/harvest/stop.rb +51 -0
  36. data/lib/abt/providers/harvest/tasks.rb +36 -0
  37. data/lib/abt/version.rb +5 -0
  38. metadata +138 -0
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Asana
6
+ class Init < BaseCommand
7
+ def self.command
8
+ 'init asana'
9
+ end
10
+
11
+ def self.description
12
+ 'Pick Asana project for current git repository'
13
+ end
14
+
15
+ def call
16
+ warn 'Loading projects'
17
+
18
+ projects # Load projects up front to make it obvious that searches are instant
19
+ project = find_search_result
20
+
21
+ remember_project_gid(project['gid'])
22
+ remember_task_gid(nil)
23
+
24
+ print_project(project)
25
+ end
26
+
27
+ private
28
+
29
+ def find_search_result
30
+ loop do
31
+ matches = matches_for_string cli.prompt('Enter search')
32
+ if matches.empty?
33
+ warn 'No matches'
34
+ next
35
+ end
36
+
37
+ warn 'Showing the 10 first matches' if matches.size > 10
38
+ choice = cli.prompt_choice 'Select a project', matches[0...10], true
39
+ break choice unless choice.nil?
40
+ end
41
+ end
42
+
43
+ def matches_for_string(string)
44
+ search_string = sanitize_string(string)
45
+
46
+ projects.select do |project|
47
+ sanitize_string(project['name']).include?(search_string)
48
+ end
49
+ end
50
+
51
+ def sanitize_string(string)
52
+ string.downcase.gsub(/[^\w]/, '')
53
+ end
54
+
55
+ def projects
56
+ @projects ||=
57
+ Asana.client.get_paged('projects', workspace: Asana.workspace_gid, archived: false)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Asana
6
+ class Move < BaseCommand
7
+ def self.command
8
+ 'move asana[:<project-gid>/<task-gid>]'
9
+ end
10
+
11
+ def self.description
12
+ 'Move current or specified task to another section (column)'
13
+ end
14
+
15
+ def call
16
+ print_task(project, task)
17
+
18
+ move_task
19
+
20
+ warn "Asana task moved to #{section['name']}"
21
+ rescue Abt::HttpError::HttpError => e
22
+ warn e
23
+ abort 'Unable to move asana task'
24
+ end
25
+
26
+ private
27
+
28
+ def task
29
+ @task ||= Asana.client.get("tasks/#{task_gid}")
30
+ end
31
+
32
+ def move_task
33
+ body = { data: { task: task_gid } }
34
+ body_json = Oj.dump(body, mode: :json)
35
+ Asana.client.post("sections/#{section['gid']}/addTask", body_json)
36
+ end
37
+
38
+ def section
39
+ @section ||= cli.prompt_choice 'Move asana task to?', sections
40
+ end
41
+
42
+ def project
43
+ @project ||= Asana.client.get("projects/#{project_gid}")
44
+ end
45
+
46
+ def sections
47
+ Asana.client.get_paged("projects/#{project_gid}/sections")
48
+ rescue Abt::HttpError::HttpError
49
+ []
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Asana
6
+ class PickTask < BaseCommand
7
+ def self.command
8
+ 'pick-task asana[:<project-gid>]'
9
+ end
10
+
11
+ def self.description
12
+ 'Pick task for current git repository'
13
+ end
14
+
15
+ def call
16
+ warn project['name']
17
+ task = cli.prompt_choice 'Select a task', tasks
18
+
19
+ remember_project_gid(project_gid) # We might have gotten the project ID as an argument
20
+ remember_task_gid(task['gid'])
21
+
22
+ print_task(project, task)
23
+ end
24
+
25
+ private
26
+
27
+ def project
28
+ @project ||= Asana.client.get("projects/#{project_gid}")
29
+ end
30
+
31
+ def tasks
32
+ @tasks ||= begin
33
+ section = cli.prompt_choice 'Which section?', sections
34
+ Asana.client.get_paged('tasks', section: section['gid'])
35
+ end
36
+ end
37
+
38
+ def sections
39
+ Asana.client.get_paged("projects/#{project_gid}/sections")
40
+ rescue Abt::HttpError::HttpError
41
+ []
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Asana
6
+ class Projects < BaseCommand
7
+ def self.command
8
+ 'projects asana'
9
+ end
10
+
11
+ def self.description
12
+ 'List all available projects - useful for piping into grep etc.'
13
+ end
14
+
15
+ def call
16
+ projects.map do |project|
17
+ print_project(project)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def projects
24
+ @projects ||=
25
+ Asana.client.get_paged('projects', workspace: Asana.workspace_gid, archived: false)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Asana
6
+ class Start < BaseCommand
7
+ def self.command
8
+ 'start asana[:<project-gid>/<task-gid>]'
9
+ end
10
+
11
+ def self.description
12
+ 'Set current task and move it to a section (column) of your choice'
13
+ end
14
+
15
+ def call
16
+ Current.new(arg_str: arg_str, cli: cli).call unless arg_str.nil?
17
+ Move.new(arg_str: arg_str, cli: cli).call
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Asana
6
+ class Tasks < BaseCommand
7
+ def self.command
8
+ 'tasks asana'
9
+ end
10
+
11
+ def self.description
12
+ 'List available tasks on project - useful for piping into grep etc.'
13
+ end
14
+
15
+ def call
16
+ tasks.each do |task|
17
+ print_task(project, task)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def project
24
+ @project ||= begin
25
+ Asana.client.get("projects/#{project_gid}")
26
+ end
27
+ end
28
+
29
+ def tasks
30
+ @tasks ||= Asana.client.get_paged('tasks', project: project['gid'])
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob("#{File.expand_path(__dir__)}/harvest/*.rb").sort.each do |file|
4
+ require file
5
+ end
6
+
7
+ module Abt
8
+ module Providers
9
+ class Harvest
10
+ class << self
11
+ def user_id
12
+ Abt::GitConfig.prompt_global(
13
+ 'abt.harvest.userId',
14
+ 'Please enter your harvest User ID',
15
+ 'In harvest open "My profile". The ID is the number part of the URL you are taken to'
16
+ )
17
+ end
18
+
19
+ def access_token
20
+ Abt::GitConfig.prompt_global(
21
+ 'abt.harvest.accessToken',
22
+ 'Please enter your personal harvest access token',
23
+ 'Create your personal access token here: https://id.getharvest.com/developers'
24
+ )
25
+ end
26
+
27
+ def account_id
28
+ Abt::GitConfig.prompt_global(
29
+ 'abt.harvest.accountId',
30
+ 'Please enter the harvest account id',
31
+ 'This information is shown next to your generated access token'
32
+ )
33
+ end
34
+
35
+ def clear
36
+ Abt::GitConfig.unset_local('abt.harvest.projectId')
37
+ Abt::GitConfig.unset_local('abt.harvest.taskId')
38
+ end
39
+
40
+ def clear_global
41
+ Abt::GitConfig.unset_global('abt.harvest.userId')
42
+ Abt::GitConfig.unset_global('abt.harvest.accountId')
43
+ Abt::GitConfig.unset_global('abt.harvest.accessToken')
44
+ end
45
+
46
+ def client
47
+ @client ||= Abt::HarvestClient.new(access_token: access_token, account_id: account_id)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Harvest
6
+ class BaseCommand
7
+ attr_reader :arg_str, :project_id, :task_id, :cli
8
+
9
+ def initialize(arg_str:, cli:)
10
+ @arg_str = arg_str
11
+
12
+ if arg_str.nil?
13
+ use_current_args
14
+ else
15
+ use_arg_str(arg_str)
16
+ end
17
+ @cli = cli
18
+ end
19
+
20
+ private
21
+
22
+ def print_project(project)
23
+ cli.print_provider_command(
24
+ 'harvest',
25
+ project['id'],
26
+ "#{project['client']['name']} > #{project['name']}"
27
+ )
28
+ end
29
+
30
+ def print_task(project, task)
31
+ cli.print_provider_command(
32
+ 'harvest',
33
+ "#{project['id']}/#{task['id']}",
34
+ "#{project['name']} > #{task['name']}"
35
+ )
36
+ end
37
+
38
+ def use_current_args
39
+ @project_id = Abt::GitConfig.local('abt.harvest.projectId').to_s
40
+ @project_id = nil if project_id.empty?
41
+ @task_id = Abt::GitConfig.local('abt.harvest.taskId').to_s
42
+ @task_id = nil if task_id.empty?
43
+ end
44
+
45
+ def use_arg_str(arg_str)
46
+ args = arg_str.to_s.split('/')
47
+ @project_id = args[0].to_s
48
+ @project_id = nil if project_id.empty?
49
+
50
+ return if project_id.nil?
51
+
52
+ @task_id = args[1].to_s
53
+ @task_id = nil if @task_id.empty?
54
+ end
55
+
56
+ def remember_project_id(project_id)
57
+ Abt::GitConfig.local('abt.harvest.projectId', project_id)
58
+ end
59
+
60
+ def remember_task_id(task_id)
61
+ if task_id.nil?
62
+ Abt::GitConfig.unset_local('abt.harvest.taskId')
63
+ else
64
+ Abt::GitConfig.local('abt.harvest.taskId', task_id)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Harvest
6
+ class Clear
7
+ def self.command
8
+ 'clear harvest'
9
+ end
10
+
11
+ def self.description
12
+ 'Clear project/task for current git repository'
13
+ end
14
+
15
+ def initialize(**); end
16
+
17
+ def call
18
+ warn 'Clearing Harvest project configuration'
19
+ Harvest.clear
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Harvest
6
+ class ClearGlobal
7
+ def self.command
8
+ 'clear-global harvest'
9
+ end
10
+
11
+ def self.description
12
+ 'Clear all global configuration'
13
+ end
14
+
15
+ def initialize(**); end
16
+
17
+ def call
18
+ warn 'Clearing Harvest project configuration'
19
+ Harvest.clear_global
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ class Harvest
6
+ class Current < BaseCommand
7
+ def self.command
8
+ 'current harvest[:<project-id>[/<task-id>]]'
9
+ end
10
+
11
+ def self.description
12
+ 'Get or set project and or task for current git repository'
13
+ end
14
+
15
+ def call
16
+ if arg_str.nil?
17
+ show_current_configuration
18
+ else
19
+ warn 'Updating configuration'
20
+ update_configuration
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def show_current_configuration
27
+ if project_id.nil?
28
+ warn 'No project selected'
29
+ elsif task_id.nil?
30
+ print_project(project)
31
+ else
32
+ print_task(project, task)
33
+ end
34
+ end
35
+
36
+ def update_configuration
37
+ ensure_project_is_valid!
38
+ remember_project_id(project_id)
39
+
40
+ if task_id.nil?
41
+ print_project(project)
42
+ remember_task_id(nil)
43
+ else
44
+ ensure_task_is_valid!
45
+ remember_task_id(task_id)
46
+
47
+ print_task(project, task)
48
+ end
49
+ end
50
+
51
+ def ensure_project_is_valid!
52
+ abort "Invalid project: #{project_id}" if project.nil?
53
+ end
54
+
55
+ def ensure_task_is_valid!
56
+ abort "Invalid task: #{task_id}" if task.nil?
57
+ end
58
+
59
+ def project
60
+ @project ||= Harvest.client.get("projects/#{project_id}")
61
+ end
62
+
63
+ def task
64
+ project_task_assignments
65
+ .map { |assignment| assignment['task'] }
66
+ .find { |task| task['id'].to_s == task_id }
67
+ end
68
+
69
+ def project_task_assignments
70
+ @project_task_assignments ||= begin
71
+ Harvest.client.get_paged("projects/#{project_id}/task_assignments", is_active: true)
72
+ rescue Abt::HttpError::HttpError # rubocop:disable Layout/RescueEnsureAlignment
73
+ []
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end