abt-cli 0.0.13 → 0.0.18

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +1 -1
  3. data/lib/abt.rb +6 -3
  4. data/lib/abt/cli.rb +91 -53
  5. data/lib/abt/cli/arguments_parser.rb +70 -0
  6. data/lib/abt/cli/base_command.rb +61 -0
  7. data/lib/abt/cli/{dialogs.rb → prompt.rb} +37 -18
  8. data/lib/abt/docs.rb +30 -24
  9. data/lib/abt/docs/cli.rb +42 -11
  10. data/lib/abt/docs/markdown.rb +38 -11
  11. data/lib/abt/git_config.rb +25 -14
  12. data/lib/abt/helpers.rb +1 -1
  13. data/lib/abt/providers/asana/base_command.rb +13 -13
  14. data/lib/abt/providers/asana/commands/add.rb +6 -6
  15. data/lib/abt/providers/asana/commands/branch_name.rb +44 -0
  16. data/lib/abt/providers/asana/commands/clear.rb +17 -6
  17. data/lib/abt/providers/asana/commands/current.rb +3 -3
  18. data/lib/abt/providers/asana/commands/finalize.rb +3 -3
  19. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +3 -3
  20. data/lib/abt/providers/asana/commands/init.rb +5 -5
  21. data/lib/abt/providers/asana/commands/pick.rb +16 -8
  22. data/lib/abt/providers/asana/commands/projects.rb +3 -3
  23. data/lib/abt/providers/asana/commands/share.rb +5 -5
  24. data/lib/abt/providers/asana/commands/start.rb +14 -8
  25. data/lib/abt/providers/asana/commands/tasks.rb +3 -3
  26. data/lib/abt/providers/asana/configuration.rb +8 -16
  27. data/lib/abt/providers/devops/base_command.rb +14 -15
  28. data/lib/abt/providers/devops/commands/boards.rb +6 -4
  29. data/lib/abt/providers/devops/commands/branch_name.rb +45 -0
  30. data/lib/abt/providers/devops/commands/clear.rb +17 -6
  31. data/lib/abt/providers/devops/commands/current.rb +3 -3
  32. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +3 -3
  33. data/lib/abt/providers/devops/commands/init.rb +5 -5
  34. data/lib/abt/providers/devops/commands/pick.rb +14 -7
  35. data/lib/abt/providers/devops/commands/share.rb +4 -4
  36. data/lib/abt/providers/devops/commands/work-items.rb +3 -3
  37. data/lib/abt/providers/devops/configuration.rb +7 -15
  38. data/lib/abt/providers/git.rb +19 -0
  39. data/lib/abt/providers/git/commands/branch.rb +74 -0
  40. data/lib/abt/providers/harvest/base_command.rb +13 -13
  41. data/lib/abt/providers/harvest/commands/clear.rb +17 -6
  42. data/lib/abt/providers/harvest/commands/current.rb +3 -3
  43. data/lib/abt/providers/harvest/commands/init.rb +5 -5
  44. data/lib/abt/providers/harvest/commands/pick.rb +15 -7
  45. data/lib/abt/providers/harvest/commands/projects.rb +3 -3
  46. data/lib/abt/providers/harvest/commands/share.rb +5 -5
  47. data/lib/abt/providers/harvest/commands/start.rb +6 -42
  48. data/lib/abt/providers/harvest/commands/stop.rb +3 -3
  49. data/lib/abt/providers/harvest/commands/tasks.rb +3 -3
  50. data/lib/abt/providers/harvest/commands/track.rb +49 -11
  51. data/lib/abt/providers/harvest/configuration.rb +7 -13
  52. data/lib/abt/version.rb +1 -1
  53. metadata +9 -7
  54. data/lib/abt/cli/io.rb +0 -23
  55. data/lib/abt/providers/asana/commands/clear_global.rb +0 -24
  56. data/lib/abt/providers/devops/commands/clear_global.rb +0 -24
  57. data/lib/abt/providers/harvest/commands/clear_global.rb +0 -24
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class BranchName < BaseCommand
8
+ def self.usage
9
+ 'abt branch-name devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
10
+ end
11
+
12
+ def self.description
13
+ 'Suggest a git branch name for the current/specified work-item.'
14
+ end
15
+
16
+ def perform
17
+ require_work_item!
18
+
19
+ cli.puts name
20
+ rescue HttpError::NotFoundError
21
+ args = [organization_name, project_name, board_id, work_item_id].compact
22
+ cli.warn 'Unable to find work item for configuration:'
23
+ cli.abort "devops:#{args.join('/')}"
24
+ end
25
+
26
+ private
27
+
28
+ def name
29
+ str = work_item['id']
30
+ str += '-'
31
+ str += work_item['name'].downcase.gsub(/[^\w]/, '-')
32
+ str.gsub(/-+/, '-')
33
+ end
34
+
35
+ def work_item
36
+ @work_item ||= begin
37
+ work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
38
+ sanitize_work_item(work_item)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -5,17 +5,28 @@ 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]
19
30
  end
20
31
  end
21
32
  end
@@ -5,15 +5,15 @@ 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
17
  require_board!
18
18
 
19
19
  if same_args_as_config? || !config.local_available?
@@ -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 = {
@@ -8,21 +8,21 @@ 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
19
+ def perform
20
20
  cli.abort 'Must be run inside a git repository' unless config.local_available?
21
21
 
22
22
  @organization_name = config.organization_name = organization_name_from_url
23
23
  @project_name = config.project_name = project_name_from_url
24
24
 
25
- board = cli.prompt_choice 'Select a project work board', boards
25
+ board = cli.prompt.choice 'Select a project work board', boards
26
26
 
27
27
  config.board_id = board['id']
28
28
 
@@ -52,7 +52,7 @@ module Abt
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
  '',
@@ -5,25 +5,32 @@ 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
16
+ def self.flags
17
+ [
18
+ ['-d', '--dry-run', 'Keep existing configuration']
19
+ ]
20
+ end
21
+
22
+ def perform
17
23
  cli.abort 'Must be run inside a git repository' unless config.local_available?
18
24
  require_board!
19
25
 
20
26
  cli.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
+ update_config!(work_item)
27
34
  end
28
35
 
29
36
  private
@@ -37,7 +44,7 @@ module Abt
37
44
 
38
45
  def select_work_item
39
46
  loop do
40
- column = cli.prompt_choice 'Which column?', columns
47
+ column = cli.prompt.choice 'Which column?', columns
41
48
  cli.warn 'Fetching work items...'
42
49
  work_items = work_items_in_column(column)
43
50
 
@@ -46,7 +53,7 @@ module Abt
46
53
  next
47
54
  end
48
55
 
49
- work_item = cli.prompt_choice 'Select a work item', work_items, true
56
+ work_item = cli.prompt.choice 'Select a work item', work_items, true
50
57
  return work_item if work_item
51
58
  end
52
59
  end
@@ -5,19 +5,19 @@ 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
13
  'Print DevOps config string'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  require_work_item!
18
18
 
19
19
  args = [organization_name, project_name, board_id, work_item_id].compact
20
- cli.print_provider_command('devops', args.join('/'))
20
+ cli.print_ari('devops', args.join('/'))
21
21
  end
22
22
  end
23
23
  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|
@@ -34,7 +34,7 @@ module Abt
34
34
  def organization_name=(value)
35
35
  return if organization_name == value
36
36
 
37
- clear_local
37
+ clear_local(verbose: false)
38
38
  git['organizationName'] = value unless value.nil?
39
39
  end
40
40
 
@@ -57,20 +57,12 @@ module Abt
57
57
  git['workItemId'] = value
58
58
  end
59
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
60
+ def clear_local(verbose: true)
61
+ git.clear(output: verbose ? cli.err_output : nil)
67
62
  end
68
63
 
69
- def clear_global
70
- git.global.keys.each do |key|
71
- cli.puts 'Deleting configuration: ' + key
72
- git.global[key] = nil
73
- end
64
+ def clear_global(verbose: true)
65
+ git.global.clear(output: verbose ? cli.err_output : nil)
74
66
  end
75
67
 
76
68
  def username_for_organization(organization_name)
@@ -78,7 +70,7 @@ module Abt
78
70
 
79
71
  return git.global[username_key] unless git.global[username_key].nil?
80
72
 
81
- git.global[username_key] = cli.prompt([
73
+ git.global[username_key] = cli.prompt.text([
82
74
  "Please provide your username for the DevOps organization (#{organization_name}).",
83
75
  '',
84
76
  'Enter username'
@@ -90,7 +82,7 @@ module Abt
90
82
 
91
83
  return git.global[access_token_key] unless git.global[access_token_key].nil?
92
84
 
93
- git.global[access_token_key] = cli.prompt([
85
+ git.global[access_token_key] = cli.prompt.text([
94
86
  "Please provide your personal access token for the DevOps organization (#{organization_name}).",
95
87
  '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
88
  '',
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob("#{File.expand_path(__dir__)}/git/*.rb").sort.each { |file| require file }
4
+ Dir.glob("#{File.expand_path(__dir__)}/git/commands/*.rb").sort.each { |file| require file }
5
+
6
+ module Abt
7
+ module Providers
8
+ module Git
9
+ def self.command_names
10
+ Commands.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
11
+ end
12
+
13
+ def self.command_class(name)
14
+ const_name = Helpers.command_to_const(name)
15
+ Commands.const_get(const_name) if Commands.const_defined?(const_name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Git
6
+ module Commands
7
+ class Branch < Abt::Cli::BaseCommand
8
+ def self.usage
9
+ 'abt branch git <scheme>[:<path>]'
10
+ end
11
+
12
+ def self.description
13
+ 'Switch branch. Uses a compatible scheme to generate the branch-name: E.g. `abt branch git asana`'
14
+ end
15
+
16
+ def perform
17
+ create_and_switch unless switch
18
+ cli.warn "Switched to #{branch_name}"
19
+ end
20
+
21
+ private
22
+
23
+ def switch
24
+ success = false
25
+ Open3.popen3("git switch #{branch_name}") do |_i, _o, _error_output, thread|
26
+ success = thread.value.success?
27
+ end
28
+ success
29
+ end
30
+
31
+ def create_and_switch
32
+ cli.warn "No such branch: #{branch_name}"
33
+ cli.abort('Aborting') unless cli.prompt.boolean 'Create branch?'
34
+
35
+ Open3.popen3("git switch -c #{branch_name}") do |_i, _o, _e, thread|
36
+ thread.value
37
+ end
38
+ end
39
+
40
+ def branch_name # rubocop:disable Metrics/MethodLength
41
+ @branch_name ||= begin
42
+ if branch_names_from_aris.empty?
43
+ cli.abort [
44
+ 'None of the specified ARIs responded to `branch-name`.',
45
+ 'Did you add compatible scheme? e.g.:',
46
+ ' abt branch git asana',
47
+ ' abt branch git devops'
48
+ ].join("\n")
49
+ end
50
+
51
+ if branch_names_from_aris.length > 1
52
+ cli.abort [
53
+ 'Got branch names from multiple ARIs, only one is supported',
54
+ 'Branch names were:',
55
+ *branch_names_from_aris.map { |name| " #{name}" }
56
+ ].join("\n")
57
+ end
58
+
59
+ branch_names_from_aris.first
60
+ end
61
+ end
62
+
63
+ def branch_names_from_aris
64
+ input = StringIO.new(cli.aris.to_s)
65
+ output = StringIO.new
66
+ Abt::Cli.new(argv: ['branch-name'], output: output, input: input).perform
67
+
68
+ output.string.lines.map(&:strip).compact
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -3,19 +3,19 @@
3
3
  module Abt
4
4
  module Providers
5
5
  module Harvest
6
- class BaseCommand
7
- attr_reader :arg_str, :project_id, :task_id, :cli, :config
6
+ class BaseCommand < Abt::Cli::BaseCommand
7
+ attr_reader :path, :flags, :project_id, :task_id, :cli, :config
8
+
9
+ def initialize(path:, cli:, **)
10
+ super
8
11
 
9
- def initialize(arg_str:, cli:)
10
- @arg_str = arg_str
11
12
  @config = Configuration.new(cli: cli)
12
13
 
13
- if arg_str.nil?
14
- use_current_args
14
+ if path.nil?
15
+ use_current_path
15
16
  else
16
- use_arg_str(arg_str)
17
+ use_path(path)
17
18
  end
18
- @cli = cli
19
19
  end
20
20
 
21
21
  private
@@ -36,7 +36,7 @@ module Abt
36
36
  end
37
37
 
38
38
  def print_project(project)
39
- cli.print_provider_command(
39
+ cli.print_ari(
40
40
  'harvest',
41
41
  project['id'],
42
42
  "#{project['client']['name']} > #{project['name']}"
@@ -44,20 +44,20 @@ module Abt
44
44
  end
45
45
 
46
46
  def print_task(project, task)
47
- cli.print_provider_command(
47
+ cli.print_ari(
48
48
  'harvest',
49
49
  "#{project['id']}/#{task['id']}",
50
50
  "#{project['name']} > #{task['name']}"
51
51
  )
52
52
  end
53
53
 
54
- def use_current_args
54
+ def use_current_path
55
55
  @project_id = config.project_id
56
56
  @task_id = config.task_id
57
57
  end
58
58
 
59
- def use_arg_str(arg_str)
60
- args = arg_str.to_s.split('/')
59
+ def use_path(path)
60
+ args = path.to_s.split('/')
61
61
  @project_id = args[0].to_s
62
62
  @project_id = nil if project_id.empty?
63
63