abt-cli 0.0.21 → 0.0.22

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