abt-cli 0.0.21 → 0.0.22

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +3 -3
  3. data/lib/abt.rb +6 -6
  4. data/lib/abt/ari.rb +1 -1
  5. data/lib/abt/ari_list.rb +1 -1
  6. data/lib/abt/base_command.rb +7 -7
  7. data/lib/abt/cli.rb +27 -40
  8. data/lib/abt/cli/arguments_parser.rb +5 -9
  9. data/lib/abt/cli/global_commands.rb +23 -0
  10. data/lib/abt/cli/global_commands/commands.rb +2 -2
  11. data/lib/abt/cli/global_commands/examples.rb +2 -2
  12. data/lib/abt/cli/global_commands/help.rb +2 -2
  13. data/lib/abt/cli/global_commands/readme.rb +2 -2
  14. data/lib/abt/cli/global_commands/share.rb +6 -6
  15. data/lib/abt/cli/global_commands/version.rb +2 -2
  16. data/lib/abt/cli/prompt.rb +51 -20
  17. data/lib/abt/docs.rb +39 -33
  18. data/lib/abt/docs/cli.rb +3 -3
  19. data/lib/abt/docs/markdown.rb +5 -5
  20. data/lib/abt/git_config.rb +4 -6
  21. data/lib/abt/providers/asana/api.rb +9 -9
  22. data/lib/abt/providers/asana/base_command.rb +8 -10
  23. data/lib/abt/providers/asana/commands/add.rb +13 -12
  24. data/lib/abt/providers/asana/commands/branch_name.rb +8 -8
  25. data/lib/abt/providers/asana/commands/clear.rb +7 -8
  26. data/lib/abt/providers/asana/commands/current.rb +14 -14
  27. data/lib/abt/providers/asana/commands/finalize.rb +11 -12
  28. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +11 -11
  29. data/lib/abt/providers/asana/commands/init.rb +8 -41
  30. data/lib/abt/providers/asana/commands/pick.rb +17 -17
  31. data/lib/abt/providers/asana/commands/projects.rb +5 -5
  32. data/lib/abt/providers/asana/commands/share.rb +5 -5
  33. data/lib/abt/providers/asana/commands/start.rb +21 -20
  34. data/lib/abt/providers/asana/commands/tasks.rb +6 -6
  35. data/lib/abt/providers/asana/configuration.rb +25 -25
  36. data/lib/abt/providers/asana/path.rb +5 -5
  37. data/lib/abt/providers/devops/api.rb +12 -12
  38. data/lib/abt/providers/devops/base_command.rb +10 -10
  39. data/lib/abt/providers/devops/commands/boards.rb +5 -7
  40. data/lib/abt/providers/devops/commands/branch_name.rb +9 -9
  41. data/lib/abt/providers/devops/commands/clear.rb +7 -8
  42. data/lib/abt/providers/devops/commands/current.rb +17 -17
  43. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +13 -13
  44. data/lib/abt/providers/devops/commands/init.rb +17 -13
  45. data/lib/abt/providers/devops/commands/pick.rb +11 -11
  46. data/lib/abt/providers/devops/commands/share.rb +5 -5
  47. data/lib/abt/providers/devops/commands/{work-items.rb → work_items.rb} +3 -3
  48. data/lib/abt/providers/devops/configuration.rb +19 -15
  49. data/lib/abt/providers/devops/path.rb +5 -4
  50. data/lib/abt/providers/git/commands/branch.rb +17 -19
  51. data/lib/abt/providers/harvest/api.rb +8 -8
  52. data/lib/abt/providers/harvest/base_command.rb +6 -8
  53. data/lib/abt/providers/harvest/commands/clear.rb +7 -8
  54. data/lib/abt/providers/harvest/commands/current.rb +13 -13
  55. data/lib/abt/providers/harvest/commands/init.rb +10 -38
  56. data/lib/abt/providers/harvest/commands/pick.rb +11 -11
  57. data/lib/abt/providers/harvest/commands/projects.rb +5 -5
  58. data/lib/abt/providers/harvest/commands/share.rb +5 -5
  59. data/lib/abt/providers/harvest/commands/start.rb +5 -3
  60. data/lib/abt/providers/harvest/commands/stop.rb +12 -12
  61. data/lib/abt/providers/harvest/commands/tasks.rb +7 -7
  62. data/lib/abt/providers/harvest/commands/track.rb +21 -20
  63. data/lib/abt/providers/harvest/configuration.rb +18 -18
  64. data/lib/abt/providers/harvest/path.rb +5 -5
  65. data/lib/abt/version.rb +1 -1
  66. metadata +6 -5
@@ -6,24 +6,22 @@ module Abt
6
6
  module Commands
7
7
  class Finalize < BaseCommand
8
8
  def self.usage
9
- 'abt finalize asana[:<project-gid>/<task-gid>]'
9
+ "abt finalize asana[:<project-gid>/<task-gid>]"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Move current/specified task to section (column) for finalized tasks'
13
+ "Move current/specified task to section (column) for finalized tasks"
14
14
  end
15
15
 
16
16
  def perform
17
- unless project_gid == config.path.project_gid
18
- abort 'This is a no-op for tasks outside the current project'
19
- end
17
+ abort("This is a no-op for tasks outside the current project") unless project_gid == config.path.project_gid
20
18
  require_task!
21
19
  print_task(project_gid, task)
22
20
 
23
21
  if task_already_in_finalized_section?
24
- warn "Task already in section: #{current_task_section['name']}"
22
+ warn("Task already in section: #{current_task_section['name']}")
25
23
  else
26
- warn "Moving task to section: #{finalized_section['name']}"
24
+ warn("Moving task to section: #{finalized_section['name']}")
27
25
  move_task
28
26
  end
29
27
  end
@@ -35,18 +33,18 @@ module Abt
35
33
  end
36
34
 
37
35
  def current_task_section
38
- task_section_membership&.dig('section')
36
+ task_section_membership&.dig("section")
39
37
  end
40
38
 
41
39
  def task_section_membership
42
- task['memberships'].find do |membership|
43
- membership.dig('section', 'gid') == config.finalized_section_gid
40
+ task["memberships"].find do |membership|
41
+ membership.dig("section", "gid") == config.finalized_section_gid
44
42
  end
45
43
  end
46
44
 
47
45
  def finalized_section
48
46
  @finalized_section ||= api.get("sections/#{config.finalized_section_gid}",
49
- opt_fields: 'name')
47
+ opt_fields: "name")
50
48
  end
51
49
 
52
50
  def move_task
@@ -57,7 +55,8 @@ module Abt
57
55
 
58
56
  def task
59
57
  @task ||= begin
60
- api.get("tasks/#{task_gid}", opt_fields: 'name,memberships.section.name,permalink_url')
58
+ api.get("tasks/#{task_gid}",
59
+ opt_fields: "name,memberships.section.name,permalink_url")
61
60
  end
62
61
  end
63
62
  end
@@ -6,11 +6,11 @@ module Abt
6
6
  module Commands
7
7
  class HarvestTimeEntryData < BaseCommand
8
8
  def self.usage
9
- 'abt harvest-time-entry-data asana[:<project-gid>/<task-gid>]'
9
+ "abt harvest-time-entry-data asana[:<project-gid>/<task-gid>]"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Print Harvest time entry data for Asana task as json. Used by harvest start script.'
13
+ "Print Harvest time entry data for Asana task as json. Used by harvest start script."
14
14
  end
15
15
 
16
16
  def perform
@@ -18,11 +18,11 @@ module Abt
18
18
  ensure_current_is_valid!
19
19
 
20
20
  body = {
21
- notes: task['name'],
21
+ notes: task["name"],
22
22
  external_reference: {
23
23
  id: task_gid.to_i,
24
24
  group_id: project_gid.to_i,
25
- permalink: task['permalink_url']
25
+ permalink: task["permalink_url"]
26
26
  }
27
27
  }
28
28
 
@@ -32,19 +32,19 @@ module Abt
32
32
  private
33
33
 
34
34
  def ensure_current_is_valid!
35
- abort "Invalid task gid: #{task_gid}" if task.nil?
35
+ abort("Invalid task gid: #{task_gid}") if task.nil?
36
36
 
37
- return if task['memberships'].any? { |m| m.dig('project', 'gid') == project_gid }
37
+ return if task["memberships"].any? { |m| m.dig("project", "gid") == project_gid }
38
38
 
39
- abort "Invalid or unmatching project gid: #{project_gid}"
39
+ abort("Invalid or unmatching project gid: #{project_gid}")
40
40
  end
41
41
 
42
42
  def task
43
43
  @task ||= begin
44
- warn 'Fetching task...'
45
- api.get("tasks/#{task_gid}", opt_fields: 'name,permalink_url,memberships.project')
46
- rescue Abt::HttpError::NotFoundError
47
- nil
44
+ warn("Fetching task...")
45
+ api.get("tasks/#{task_gid}", opt_fields: "name,permalink_url,memberships.project")
46
+ rescue Abt::HttpError::NotFoundError
47
+ nil
48
48
  end
49
49
  end
50
50
  end
@@ -6,66 +6,33 @@ module Abt
6
6
  module Commands
7
7
  class Init < BaseCommand
8
8
  def self.usage
9
- 'abt init asana'
9
+ "abt init asana"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Pick Asana project for current git repository'
14
- end
15
-
16
- def initialize(cli:, **)
17
- @config = Configuration.new(cli: cli)
18
- @cli = cli
13
+ "Pick Asana project for current git repository"
19
14
  end
20
15
 
21
16
  def perform
22
- abort 'Must be run inside a git repository' unless config.local_available?
17
+ abort("Must be run inside a git repository") unless config.local_available?
23
18
 
24
19
  projects # Load projects up front to make it obvious that searches are instant
25
- project = find_search_result
20
+ project = cli.prompt.search("Select a project", projects)
26
21
 
27
- config.path = Path.from_ids(project['gid'])
22
+ config.path = Path.from_ids(project["gid"])
28
23
 
29
24
  print_project(project)
30
25
  end
31
26
 
32
27
  private
33
28
 
34
- def find_search_result
35
- warn 'Select a project'
36
-
37
- loop do
38
- matches = matches_for_string cli.prompt.text('Enter search')
39
- if matches.empty?
40
- warn 'No matches'
41
- next
42
- end
43
-
44
- warn 'Showing the 10 first matches' if matches.size > 10
45
- choice = cli.prompt.choice 'Select a project', matches[0...10], true
46
- break choice unless choice.nil?
47
- end
48
- end
49
-
50
- def matches_for_string(string)
51
- search_string = sanitize_string(string)
52
-
53
- projects.select do |project|
54
- sanitize_string(project['name']).include?(search_string)
55
- end
56
- end
57
-
58
- def sanitize_string(string)
59
- string.downcase.gsub(/[^\w]/, '')
60
- end
61
-
62
29
  def projects
63
30
  @projects ||= begin
64
- warn 'Fetching projects...'
65
- api.get_paged('projects',
31
+ warn("Fetching projects...")
32
+ api.get_paged("projects",
66
33
  workspace: config.workspace_gid,
67
34
  archived: false,
68
- opt_fields: 'name,permalink_url')
35
+ opt_fields: "name,permalink_url")
69
36
  end
70
37
  end
71
38
  end
@@ -6,24 +6,24 @@ module Abt
6
6
  module Commands
7
7
  class Pick < BaseCommand
8
8
  def self.usage
9
- 'abt pick asana[:<project-gid>]'
9
+ "abt pick asana[:<project-gid>]"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Pick task for current git repository'
13
+ "Pick task for current git repository"
14
14
  end
15
15
 
16
16
  def self.flags
17
17
  [
18
- ['-d', '--dry-run', 'Keep existing configuration']
18
+ ["-d", "--dry-run", "Keep existing configuration"]
19
19
  ]
20
20
  end
21
21
 
22
22
  def perform
23
- abort 'Must be run inside a git repository' unless config.local_available?
23
+ abort("Must be run inside a git repository") unless config.local_available?
24
24
  require_project!
25
25
 
26
- warn project['name']
26
+ warn(project["name"])
27
27
 
28
28
  task = select_task
29
29
 
@@ -31,47 +31,47 @@ module Abt
31
31
 
32
32
  return if flags[:"dry-run"]
33
33
 
34
- config.path = Path.from_ids(project_gid, task['gid'])
34
+ config.path = Path.from_ids(project_gid, task["gid"])
35
35
  end
36
36
 
37
37
  private
38
38
 
39
39
  def project
40
- @project ||= api.get("projects/#{project_gid}", opt_fields: 'name')
40
+ @project ||= api.get("projects/#{project_gid}", opt_fields: "name")
41
41
  end
42
42
 
43
43
  def select_task
44
44
  loop do
45
- section = cli.prompt.choice 'Which section?', sections
46
- warn 'Fetching tasks...'
45
+ section = cli.prompt.choice("Which section?", sections)
46
+ warn("Fetching tasks...")
47
47
  tasks = tasks_in_section(section)
48
48
 
49
49
  if tasks.length.zero?
50
- warn 'Section is empty'
50
+ warn("Section is empty")
51
51
  next
52
52
  end
53
53
 
54
- task = cli.prompt.choice 'Select a task', tasks, true
54
+ task = cli.prompt.choice("Select a task", tasks, nil_option: true)
55
55
  return task if task
56
56
  end
57
57
  end
58
58
 
59
59
  def tasks_in_section(section)
60
60
  tasks = api.get_paged(
61
- 'tasks',
62
- section: section['gid'],
63
- opt_fields: 'name,completed,permalink_url'
61
+ "tasks",
62
+ section: section["gid"],
63
+ opt_fields: "name,completed,permalink_url"
64
64
  )
65
65
 
66
66
  # The below filtering is the best we can do with Asanas api, see this:
67
67
  # https://forum.asana.com/t/tasks-query-completed-since-is-broken-for-sections/21461
68
- tasks.select { |task| !task['completed'] }
68
+ tasks.reject { |task| task["completed"] }
69
69
  end
70
70
 
71
71
  def sections
72
72
  @sections ||= begin
73
- warn 'Fetching sections...'
74
- api.get_paged("projects/#{project_gid}/sections", opt_fields: 'name')
73
+ warn("Fetching sections...")
74
+ api.get_paged("projects/#{project_gid}/sections", opt_fields: "name")
75
75
  end
76
76
  end
77
77
  end
@@ -6,11 +6,11 @@ module Abt
6
6
  module Commands
7
7
  class Projects < BaseCommand
8
8
  def self.usage
9
- 'abt projects asana'
9
+ "abt projects asana"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'List all available projects - useful for piping into grep etc.'
13
+ "List all available projects - useful for piping into grep etc."
14
14
  end
15
15
 
16
16
  def perform
@@ -23,12 +23,12 @@ module Abt
23
23
 
24
24
  def projects
25
25
  @projects ||= begin
26
- warn 'Fetching projects...'
26
+ warn("Fetching projects...")
27
27
  api.get_paged(
28
- 'projects',
28
+ "projects",
29
29
  workspace: config.workspace_gid,
30
30
  archived: false,
31
- opt_fields: 'name'
31
+ opt_fields: "name"
32
32
  )
33
33
  end
34
34
  end
@@ -6,18 +6,18 @@ module Abt
6
6
  module Commands
7
7
  class Share < BaseCommand
8
8
  def self.usage
9
- 'abt share asana[:<project-gid>[/<task-gid>]]'
9
+ "abt share asana[:<project-gid>[/<task-gid>]]"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Print project/task ARI'
13
+ "Print project/task ARI"
14
14
  end
15
15
 
16
16
  def perform
17
- if path != ''
18
- cli.print_ari('asana', path)
17
+ if path != ""
18
+ cli.print_ari("asana", path)
19
19
  elsif cli.output.isatty
20
- warn 'No configuration for project. Did you initialize Asana?'
20
+ warn("No configuration for project. Did you initialize Asana?")
21
21
  end
22
22
  end
23
23
  end
@@ -6,16 +6,16 @@ module Abt
6
6
  module Commands
7
7
  class Start < BaseCommand
8
8
  def self.usage
9
- 'abt start asana[:<project-gid>/<task-gid>]'
9
+ "abt start asana[:<project-gid>/<task-gid>]"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Move current or specified task to WIP section (column) and assign it to you'
13
+ "Move current or specified task to WIP section (column) and assign it to you"
14
14
  end
15
15
 
16
16
  def self.flags
17
17
  [
18
- ['-s', '--set', 'Set specified task as current']
18
+ ["-s", "--set", "Set specified task as current"]
19
19
  ]
20
20
  end
21
21
 
@@ -38,33 +38,33 @@ module Abt
38
38
  return unless config.local_available?
39
39
 
40
40
  config.path = path
41
- warn 'Current task updated'
41
+ warn("Current task updated")
42
42
  end
43
43
 
44
44
  def update_assignee_if_needed
45
- current_assignee = task.dig('assignee')
45
+ current_assignee = task["assignee"]
46
46
 
47
47
  if current_assignee.nil?
48
- warn "Assigning task to user: #{current_user['name']}"
48
+ warn("Assigning task to user: #{current_user['name']}")
49
49
  update_assignee
50
- elsif current_assignee['gid'] == current_user['gid']
51
- warn 'You are already assigned to this task'
52
- elsif cli.prompt.boolean "Task is assigned to: #{current_assignee['name']}, take over?"
53
- warn "Reassigning task to user: #{current_user['name']}"
50
+ elsif current_assignee["gid"] == current_user["gid"]
51
+ warn("You are already assigned to this task")
52
+ elsif cli.prompt.boolean("Task is assigned to: #{current_assignee['name']}, take over?")
53
+ warn("Reassigning task to user: #{current_user['name']}")
54
54
  update_assignee
55
55
  end
56
56
  end
57
57
 
58
58
  def move_if_needed
59
59
  unless project_gid == config.path.project_gid
60
- warn 'Task was not moved, this is not implemented for tasks outside current project'
60
+ warn("Task was not moved, this is not implemented for tasks outside current project")
61
61
  return
62
62
  end
63
63
 
64
64
  if task_already_in_wip_section?
65
- warn "Task already in section: #{current_task_section['name']}"
65
+ warn("Task already in section: #{current_task_section['name']}")
66
66
  else
67
- warn "Moving task to section: #{wip_section['name']}"
67
+ warn("Moving task to section: #{wip_section['name']}")
68
68
  move_task
69
69
  end
70
70
  end
@@ -74,17 +74,17 @@ module Abt
74
74
  end
75
75
 
76
76
  def current_task_section
77
- task_section_membership&.dig('section')
77
+ task_section_membership&.dig("section")
78
78
  end
79
79
 
80
80
  def task_section_membership
81
- task['memberships'].find do |membership|
82
- membership.dig('section', 'gid') == config.wip_section_gid
81
+ task["memberships"].find do |membership|
82
+ membership.dig("section", "gid") == config.wip_section_gid
83
83
  end
84
84
  end
85
85
 
86
86
  def wip_section
87
- @wip_section ||= api.get("sections/#{config.wip_section_gid}", opt_fields: 'name')
87
+ @wip_section ||= api.get("sections/#{config.wip_section_gid}", opt_fields: "name")
88
88
  end
89
89
 
90
90
  def move_task
@@ -94,17 +94,18 @@ module Abt
94
94
  end
95
95
 
96
96
  def update_assignee
97
- body = { data: { assignee: current_user['gid'] } }
97
+ body = { data: { assignee: current_user["gid"] } }
98
98
  body_json = Oj.dump(body, mode: :json)
99
99
  api.put("tasks/#{task_gid}", body_json)
100
100
  end
101
101
 
102
102
  def current_user
103
- @current_user ||= api.get('users/me', opt_fields: 'name')
103
+ @current_user ||= api.get("users/me", opt_fields: "name")
104
104
  end
105
105
 
106
106
  def task
107
- @task ||= api.get("tasks/#{task_gid}", opt_fields: 'name,memberships.section.name,assignee.name,permalink_url')
107
+ @task ||= api.get("tasks/#{task_gid}",
108
+ opt_fields: "name,memberships.section.name,assignee.name,permalink_url")
108
109
  end
109
110
  end
110
111
  end
@@ -6,11 +6,11 @@ module Abt
6
6
  module Commands
7
7
  class Tasks < BaseCommand
8
8
  def self.usage
9
- 'abt tasks asana'
9
+ "abt tasks asana"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'List available tasks on project - useful for piping into grep etc.'
13
+ "List available tasks on project - useful for piping into grep etc."
14
14
  end
15
15
 
16
16
  def perform
@@ -25,15 +25,15 @@ module Abt
25
25
 
26
26
  def project
27
27
  @project ||= begin
28
- api.get("projects/#{project_gid}", opt_fields: 'name')
28
+ api.get("projects/#{project_gid}", opt_fields: "name")
29
29
  end
30
30
  end
31
31
 
32
32
  def tasks
33
33
  @tasks ||= begin
34
- warn 'Fetching tasks...'
35
- tasks = api.get_paged('tasks', project: project['gid'], opt_fields: 'name,completed')
36
- tasks.select { |task| !task['completed'] }
34
+ warn("Fetching tasks...")
35
+ tasks = api.get_paged("tasks", project: project["gid"], opt_fields: "name,completed")
36
+ tasks.reject { |task| task["completed"] }
37
37
  end
38
38
  end
39
39
  end