abt-cli 0.0.14 → 0.0.19

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +1 -1
  3. data/lib/abt.rb +4 -3
  4. data/lib/abt/ari.rb +20 -0
  5. data/lib/abt/ari_list.rb +13 -0
  6. data/lib/abt/base_command.rb +63 -0
  7. data/lib/abt/cli.rb +89 -54
  8. data/lib/abt/cli/arguments_parser.rb +48 -0
  9. data/lib/abt/cli/{dialogs.rb → prompt.rb} +38 -18
  10. data/lib/abt/docs.rb +35 -28
  11. data/lib/abt/docs/cli.rb +42 -11
  12. data/lib/abt/docs/markdown.rb +38 -11
  13. data/lib/abt/git_config.rb +26 -31
  14. data/lib/abt/providers/asana/base_command.rb +17 -37
  15. data/lib/abt/providers/asana/commands/add.rb +15 -13
  16. data/lib/abt/providers/asana/commands/{branch-name.rb → branch_name.rb} +12 -7
  17. data/lib/abt/providers/asana/commands/clear.rb +19 -6
  18. data/lib/abt/providers/asana/commands/current.rb +22 -37
  19. data/lib/abt/providers/asana/commands/finalize.rb +6 -6
  20. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +12 -7
  21. data/lib/abt/providers/asana/commands/init.rb +11 -11
  22. data/lib/abt/providers/asana/commands/pick.rb +30 -17
  23. data/lib/abt/providers/asana/commands/projects.rb +4 -4
  24. data/lib/abt/providers/asana/commands/share.rb +5 -9
  25. data/lib/abt/providers/asana/commands/start.rb +27 -19
  26. data/lib/abt/providers/asana/commands/tasks.rb +7 -6
  27. data/lib/abt/providers/asana/configuration.rb +23 -37
  28. data/lib/abt/providers/asana/path.rb +36 -0
  29. data/lib/abt/providers/devops/api.rb +12 -0
  30. data/lib/abt/providers/devops/base_command.rb +18 -44
  31. data/lib/abt/providers/devops/commands/boards.rb +7 -5
  32. data/lib/abt/providers/devops/commands/{branch-name.rb → branch_name.rb} +10 -6
  33. data/lib/abt/providers/devops/commands/clear.rb +19 -6
  34. data/lib/abt/providers/devops/commands/current.rb +17 -41
  35. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +12 -4
  36. data/lib/abt/providers/devops/commands/init.rb +20 -20
  37. data/lib/abt/providers/devops/commands/pick.rb +18 -18
  38. data/lib/abt/providers/devops/commands/share.rb +6 -7
  39. data/lib/abt/providers/devops/commands/work-items.rb +4 -4
  40. data/lib/abt/providers/devops/configuration.rb +20 -57
  41. data/lib/abt/providers/devops/path.rb +50 -0
  42. data/lib/abt/providers/git/commands/branch.rb +28 -28
  43. data/lib/abt/providers/harvest/base_command.rb +18 -36
  44. data/lib/abt/providers/harvest/commands/clear.rb +19 -6
  45. data/lib/abt/providers/harvest/commands/current.rb +27 -34
  46. data/lib/abt/providers/harvest/commands/init.rb +10 -11
  47. data/lib/abt/providers/harvest/commands/pick.rb +16 -9
  48. data/lib/abt/providers/harvest/commands/projects.rb +4 -4
  49. data/lib/abt/providers/harvest/commands/share.rb +7 -11
  50. data/lib/abt/providers/harvest/commands/start.rb +6 -42
  51. data/lib/abt/providers/harvest/commands/stop.rb +10 -10
  52. data/lib/abt/providers/harvest/commands/tasks.rb +7 -4
  53. data/lib/abt/providers/harvest/commands/track.rb +66 -21
  54. data/lib/abt/providers/harvest/configuration.rb +23 -38
  55. data/lib/abt/providers/harvest/path.rb +36 -0
  56. data/lib/abt/version.rb +1 -1
  57. metadata +12 -9
  58. data/lib/abt/cli/io.rb +0 -23
  59. data/lib/abt/providers/asana/commands/clear_global.rb +0 -24
  60. data/lib/abt/providers/devops/commands/clear_global.rb +0 -24
  61. data/lib/abt/providers/harvest/commands/clear_global.rb +0 -24
@@ -5,17 +5,30 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class Clear < BaseCommand
8
- def self.command
9
- 'clear devops'
8
+ def self.usage
9
+ 'abt clear devops'
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Clear DevOps config for current git repository'
13
+ 'Clear DevOps configuration'
14
14
  end
15
15
 
16
- def call
17
- cli.warn 'Clearing configuration'
18
- config.clear_local
16
+ def self.flags
17
+ [
18
+ ['-g', '--global', 'Clear global instead of local DevOp configuration (credentials etc.)'],
19
+ ['-a', '--all', 'Clear all DevOp configuration']
20
+ ]
21
+ end
22
+
23
+ def perform
24
+ if flags[:global] && flags[:all]
25
+ abort('Flags --global and --all cannot be used at the same time')
26
+ end
27
+
28
+ config.clear_local unless flags[:global]
29
+ config.clear_global if flags[:global] || flags[:all]
30
+
31
+ warn 'Configuration cleared'
19
32
  end
20
33
  end
21
34
  end
@@ -5,72 +5,48 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class Current < BaseCommand
8
- def self.command
9
- 'current devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
8
+ def self.usage
9
+ 'abt current devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Get or set DevOps configuration for current git repository'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
+ abort 'Must be run inside a git repository' unless config.local_available?
18
+
17
19
  require_board!
20
+ ensure_valid_configuration!
18
21
 
19
- if same_args_as_config? || !config.local_available?
20
- show_current_configuration
21
- else
22
- cli.warn 'Updating configuration'
23
- update_configuration
22
+ if path != config.path && config.local_available?
23
+ config.path = path
24
+ warn 'Configuration updated'
24
25
  end
25
- end
26
-
27
- private
28
26
 
29
- def show_current_configuration
30
- if work_item_id.nil?
31
- print_board(organization_name, project_name, board)
32
- else
33
- print_work_item(organization_name, project_name, board, work_item)
34
- end
27
+ print_configuration
35
28
  end
36
29
 
37
- def update_configuration
38
- ensure_board_is_valid!
30
+ private
39
31
 
32
+ def print_configuration
40
33
  if work_item_id.nil?
41
- update_board_config
42
- config.work_item_id = nil
43
-
44
34
  print_board(organization_name, project_name, board)
45
35
  else
46
- ensure_work_item_is_valid!
47
-
48
- update_board_config
49
- config.work_item_id = work_item_id
50
-
51
36
  print_work_item(organization_name, project_name, board, work_item)
52
37
  end
53
38
  end
54
39
 
55
- def update_board_config
56
- config.organization_name = organization_name
57
- config.project_name = project_name
58
- config.board_id = board_id
59
- end
60
-
61
- def ensure_board_is_valid!
40
+ def ensure_valid_configuration!
62
41
  if board.nil?
63
- cli.abort 'Board could not be found, ensure that settings for organization, project, and board are correct'
42
+ abort 'Board could not be found, ensure that settings for organization, project, and board are correct'
64
43
  end
65
- end
66
-
67
- def ensure_work_item_is_valid!
68
- cli.abort "No such work item: ##{work_item_id}" if work_item.nil?
44
+ abort "No such work item: ##{work_item_id}" if work_item_id && work_item.nil?
69
45
  end
70
46
 
71
47
  def board
72
48
  @board ||= begin
73
- cli.warn 'Fetching board...'
49
+ warn 'Fetching board...'
74
50
  api.get("work/boards/#{board_id}")
75
51
  rescue HttpError::NotFoundError
76
52
  nil
@@ -79,7 +55,7 @@ module Abt
79
55
 
80
56
  def work_item
81
57
  @work_item ||= begin
82
- cli.warn 'Fetching work item...'
58
+ warn 'Fetching work item...'
83
59
  work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
84
60
  sanitize_work_item(work_item)
85
61
  rescue HttpError::NotFoundError
@@ -5,15 +5,15 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class HarvestTimeEntryData < BaseCommand
8
- def self.command
9
- 'harvest-time-entry-data devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
8
+ def self.usage
9
+ 'abt harvest-time-entry-data devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Print Harvest time entry data for DevOps work item as json. Used by harvest start script.'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  require_work_item!
18
18
 
19
19
  body = {
@@ -25,7 +25,15 @@ module Abt
25
25
  }
26
26
  }
27
27
 
28
- cli.puts Oj.dump(body, mode: :json)
28
+ puts Oj.dump(body, mode: :json)
29
+ rescue HttpError::NotFoundError
30
+ args = [organization_name, project_name, board_id, work_item_id].compact
31
+
32
+ error_message = [
33
+ 'Unable to find work item for configuration:',
34
+ "devops:#{args.join('/')}"
35
+ ].join("\n")
36
+ abort error_message
29
37
  end
30
38
 
31
39
  private
@@ -8,24 +8,20 @@ module Abt
8
8
  AZURE_DEV_URL_REGEX = %r{^https://dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)}.freeze
9
9
  VS_URL_REGEX = %r{^https://(?<organization>[^.]+)\.visualstudio\.com/(?<project>[^/]+)}.freeze
10
10
 
11
- def self.command
12
- 'init devops'
11
+ def self.usage
12
+ 'abt init devops'
13
13
  end
14
14
 
15
15
  def self.description
16
16
  'Pick DevOps board for current git repository'
17
17
  end
18
18
 
19
- def call
20
- cli.abort 'Must be run inside a git repository' unless config.local_available?
19
+ def perform
20
+ abort 'Must be run inside a git repository' unless config.local_available?
21
21
 
22
- @organization_name = config.organization_name = organization_name_from_url
23
- @project_name = config.project_name = project_name_from_url
24
-
25
- board = cli.prompt_choice 'Select a project work board', boards
26
-
27
- config.board_id = board['id']
22
+ board = cli.prompt.choice 'Select a project work board', boards
28
23
 
24
+ config.path = Path.from_ids(organization_name, project_name, board['id'])
29
25
  print_board(organization_name, project_name, board)
30
26
  end
31
27
 
@@ -35,24 +31,28 @@ module Abt
35
31
  @boards ||= api.get_paged('work/boards')
36
32
  end
37
33
 
38
- def project_name_from_url
39
- if (match = AZURE_DEV_URL_REGEX.match(project_url)) ||
40
- (match = VS_URL_REGEX.match(project_url))
41
- match[:project]
34
+ def project_name
35
+ @project_name ||= begin
36
+ if (match = AZURE_DEV_URL_REGEX.match(project_url)) ||
37
+ (match = VS_URL_REGEX.match(project_url))
38
+ match[:project]
39
+ end
42
40
  end
43
41
  end
44
42
 
45
- def organization_name_from_url
46
- if (match = AZURE_DEV_URL_REGEX.match(project_url)) ||
47
- (match = VS_URL_REGEX.match(project_url))
48
- match[:organization]
43
+ def organization_name
44
+ @organization_name ||= begin
45
+ if (match = AZURE_DEV_URL_REGEX.match(project_url)) ||
46
+ (match = VS_URL_REGEX.match(project_url))
47
+ match[:organization]
48
+ end
49
49
  end
50
50
  end
51
51
 
52
52
  def project_url
53
53
  @project_url ||= begin
54
54
  loop do
55
- url = cli.prompt([
55
+ url = cli.prompt.text([
56
56
  'Please provide the URL for the devops project',
57
57
  'For instance https://{organization}.visualstudio.com/{project} or https://dev.azure.com/{organization}/{project}',
58
58
  '',
@@ -61,7 +61,7 @@ module Abt
61
61
 
62
62
  break url if AZURE_DEV_URL_REGEX =~ url || VS_URL_REGEX =~ url
63
63
 
64
- cli.warn 'Invalid URL'
64
+ warn 'Invalid URL'
65
65
  end
66
66
  end
67
67
  end
@@ -5,48 +5,48 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class Pick < BaseCommand
8
- def self.command
9
- 'pick devops[:<organization-name>/<project-name>/<board-id>]'
8
+ def self.usage
9
+ 'abt pick devops[:<organization-name>/<project-name>/<board-id>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Pick work item for current git repository'
14
14
  end
15
15
 
16
- def call
17
- cli.abort 'Must be run inside a git repository' unless config.local_available?
16
+ def self.flags
17
+ [
18
+ ['-d', '--dry-run', 'Keep existing configuration']
19
+ ]
20
+ end
21
+
22
+ def perform
23
+ abort 'Must be run inside a git repository' unless config.local_available?
18
24
  require_board!
19
25
 
20
- cli.warn "#{project_name} - #{board['name']}"
26
+ warn "#{project_name} - #{board['name']}"
21
27
 
22
28
  work_item = select_work_item
29
+ print_work_item(organization_name, project_name, board, work_item)
23
30
 
24
- update_config!(work_item)
31
+ return if flags[:"dry-run"]
25
32
 
26
- print_work_item(organization_name, project_name, board, work_item)
33
+ config.path = Path.from_ids(organization_name, project_name, board_id, work_item['id'])
27
34
  end
28
35
 
29
36
  private
30
37
 
31
- def update_config!(work_item)
32
- config.organization_name = organization_name
33
- config.project_name = project_name
34
- config.board_id = board_id
35
- config.work_item_id = work_item['id']
36
- end
37
-
38
38
  def select_work_item
39
39
  loop do
40
- column = cli.prompt_choice 'Which column?', columns
41
- cli.warn 'Fetching work items...'
40
+ column = cli.prompt.choice 'Which column?', columns
41
+ warn 'Fetching work items...'
42
42
  work_items = work_items_in_column(column)
43
43
 
44
44
  if work_items.length.zero?
45
- cli.warn 'Section is empty'
45
+ warn 'Section is empty'
46
46
  next
47
47
  end
48
48
 
49
- work_item = cli.prompt_choice 'Select a work item', work_items, true
49
+ work_item = cli.prompt.choice 'Select a work item', work_items, true
50
50
  return work_item if work_item
51
51
  end
52
52
  end
@@ -5,19 +5,18 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class Share < BaseCommand
8
- def self.command
9
- 'share devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
8
+ def self.usage
9
+ 'abt share devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Print DevOps config string'
13
+ 'Print DevOps ARI'
14
14
  end
15
15
 
16
- def call
17
- require_work_item!
16
+ def perform
17
+ require_board!
18
18
 
19
- args = [organization_name, project_name, board_id, work_item_id].compact
20
- cli.print_provider_command('devops', args.join('/'))
19
+ cli.print_ari('devops', path)
21
20
  end
22
21
  end
23
22
  end
@@ -5,15 +5,15 @@ module Abt
5
5
  module Devops
6
6
  module Commands
7
7
  class WorkItems < BaseCommand
8
- def self.command
9
- 'work-items devops'
8
+ def self.usage
9
+ 'abt work-items devops'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'List all work items on board - useful for piping into grep etc.'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  require_board!
18
18
 
19
19
  work_items.each do |work_item|
@@ -25,7 +25,7 @@ module Abt
25
25
 
26
26
  def work_items
27
27
  @work_items ||= begin
28
- cli.warn 'Fetching work items...'
28
+ warn 'Fetching work items...'
29
29
  api.work_item_query(
30
30
  <<~WIQL
31
31
  SELECT [System.Id]
@@ -8,77 +8,34 @@ module Abt
8
8
 
9
9
  def initialize(cli:)
10
10
  @cli = cli
11
- @git = GitConfig.new(namespace: 'abt.devops')
12
11
  end
13
12
 
14
13
  def local_available?
15
- GitConfig.local_available?
14
+ git.available?
16
15
  end
17
16
 
18
- def organization_name
19
- local_available? ? git['organizationName'] : nil
17
+ def path
18
+ Path.new(local_available? && git['path'] || '')
20
19
  end
21
20
 
22
- def project_name
23
- local_available? ? git['projectName'] : nil
21
+ def path=(new_path)
22
+ git['path'] = new_path
24
23
  end
25
24
 
26
- def board_id
27
- local_available? ? git['boardId'] : nil
25
+ def clear_local(verbose: true)
26
+ git.clear(output: verbose ? cli.err_output : nil)
28
27
  end
29
28
 
30
- def work_item_id
31
- local_available? ? git['workItemId'] : nil
32
- end
33
-
34
- def organization_name=(value)
35
- return if organization_name == value
36
-
37
- clear_local
38
- git['organizationName'] = value unless value.nil?
39
- end
40
-
41
- def project_name=(value)
42
- return if project_name == value
43
-
44
- git['projectName'] = value unless value.nil?
45
- git['boardId'] = nil
46
- git['workItemId'] = nil
47
- end
48
-
49
- def board_id=(value)
50
- return if board_id == value
51
-
52
- git['boardId'] = value unless value.nil?
53
- git['workItemId'] = nil
54
- end
55
-
56
- def work_item_id=(value)
57
- git['workItemId'] = value
58
- end
59
-
60
- def clear_local
61
- cli.abort 'No local configuration was found' unless local_available?
62
-
63
- git['organizationName'] = nil
64
- git['projectName'] = nil
65
- git['boardId'] = nil
66
- git['workItemId'] = nil
67
- end
68
-
69
- def clear_global
70
- git.global.keys.each do |key|
71
- cli.puts 'Deleting configuration: ' + key
72
- git.global[key] = nil
73
- end
29
+ def clear_global(verbose: true)
30
+ git_global.clear(output: verbose ? cli.err_output : nil)
74
31
  end
75
32
 
76
33
  def username_for_organization(organization_name)
77
34
  username_key = "organizations.#{organization_name}.username"
78
35
 
79
- return git.global[username_key] unless git.global[username_key].nil?
36
+ return git_global[username_key] unless git_global[username_key].nil?
80
37
 
81
- git.global[username_key] = cli.prompt([
38
+ git_global[username_key] = cli.prompt.text([
82
39
  "Please provide your username for the DevOps organization (#{organization_name}).",
83
40
  '',
84
41
  'Enter username'
@@ -88,9 +45,9 @@ module Abt
88
45
  def access_token_for_organization(organization_name)
89
46
  access_token_key = "organizations.#{organization_name}.accessToken"
90
47
 
91
- return git.global[access_token_key] unless git.global[access_token_key].nil?
48
+ return git_global[access_token_key] unless git_global[access_token_key].nil?
92
49
 
93
- git.global[access_token_key] = cli.prompt([
50
+ git_global[access_token_key] = cli.prompt.text([
94
51
  "Please provide your personal access token for the DevOps organization (#{organization_name}).",
95
52
  'If you don\'t have one, follow the guide here: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate',
96
53
  '',
@@ -103,7 +60,13 @@ module Abt
103
60
 
104
61
  private
105
62
 
106
- attr_reader :git
63
+ def git
64
+ @git ||= GitConfig.new('local', 'abt.devops')
65
+ end
66
+
67
+ def git_global
68
+ @git_global ||= GitConfig.new('global', 'abt.devops')
69
+ end
107
70
  end
108
71
  end
109
72
  end