abt-cli 0.0.11 → 0.0.16

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +1 -7
  3. data/lib/abt.rb +12 -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/prompt.rb +124 -0
  8. data/lib/abt/docs.rb +24 -18
  9. data/lib/abt/docs/cli.rb +42 -11
  10. data/lib/abt/docs/markdown.rb +36 -10
  11. data/lib/abt/git_config.rb +34 -19
  12. data/lib/abt/helpers.rb +1 -1
  13. data/lib/abt/providers/asana/base_command.rb +24 -13
  14. data/lib/abt/providers/asana/commands/add.rb +75 -0
  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 +6 -6
  18. data/lib/abt/providers/asana/commands/finalize.rb +4 -4
  19. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +4 -3
  20. data/lib/abt/providers/asana/commands/init.rb +5 -5
  21. data/lib/abt/providers/asana/commands/pick.rb +16 -7
  22. data/lib/abt/providers/asana/commands/projects.rb +3 -3
  23. data/lib/abt/providers/asana/commands/share.rb +8 -8
  24. data/lib/abt/providers/asana/commands/start.rb +15 -9
  25. data/lib/abt/providers/asana/commands/tasks.rb +5 -3
  26. data/lib/abt/providers/asana/configuration.rb +8 -16
  27. data/lib/abt/providers/devops/api.rb +32 -2
  28. data/lib/abt/providers/devops/base_command.rb +32 -16
  29. data/lib/abt/providers/devops/commands/boards.rb +36 -0
  30. data/lib/abt/providers/devops/commands/branch_name.rb +45 -0
  31. data/lib/abt/providers/devops/commands/clear.rb +17 -6
  32. data/lib/abt/providers/devops/commands/current.rb +6 -10
  33. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +5 -3
  34. data/lib/abt/providers/devops/commands/init.rb +5 -5
  35. data/lib/abt/providers/devops/commands/pick.rb +29 -20
  36. data/lib/abt/providers/devops/commands/share.rb +7 -13
  37. data/lib/abt/providers/devops/commands/work-items.rb +46 -0
  38. data/lib/abt/providers/devops/configuration.rb +7 -15
  39. data/lib/abt/providers/git.rb +19 -0
  40. data/lib/abt/providers/git/commands/branch.rb +74 -0
  41. data/lib/abt/providers/harvest/base_command.rb +24 -13
  42. data/lib/abt/providers/harvest/commands/clear.rb +17 -6
  43. data/lib/abt/providers/harvest/commands/current.rb +6 -6
  44. data/lib/abt/providers/harvest/commands/init.rb +5 -5
  45. data/lib/abt/providers/harvest/commands/pick.rb +15 -6
  46. data/lib/abt/providers/harvest/commands/projects.rb +3 -3
  47. data/lib/abt/providers/harvest/commands/share.rb +5 -5
  48. data/lib/abt/providers/harvest/commands/start.rb +6 -44
  49. data/lib/abt/providers/harvest/commands/stop.rb +3 -3
  50. data/lib/abt/providers/harvest/commands/tasks.rb +5 -3
  51. data/lib/abt/providers/harvest/commands/track.rb +50 -13
  52. data/lib/abt/providers/harvest/configuration.rb +7 -13
  53. data/lib/abt/version.rb +1 -1
  54. metadata +12 -7
  55. data/lib/abt/cli/dialogs.rb +0 -86
  56. data/lib/abt/cli/io.rb +0 -23
  57. data/lib/abt/providers/asana/commands/clear_global.rb +0 -24
  58. data/lib/abt/providers/devops/commands/clear_global.rb +0 -24
  59. data/lib/abt/providers/harvest/commands/clear_global.rb +0 -24
@@ -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_scheme_arguments.empty?
43
+ cli.abort [
44
+ 'None of the specified scheme arguments 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_scheme_arguments.length > 1
52
+ cli.abort [
53
+ 'Got branch names from multiple scheme arguments, only one is supported',
54
+ 'Branch names were:',
55
+ *branch_names_from_scheme_arguments.map { |name| " #{name}" }
56
+ ].join("\n")
57
+ end
58
+
59
+ branch_names_from_scheme_arguments.first
60
+ end
61
+ end
62
+
63
+ def branch_names_from_scheme_arguments
64
+ input = StringIO.new(cli.scheme_arguments.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,29 +3,40 @@
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
22
22
 
23
+ def require_project!
24
+ cli.abort 'No current/specified project. Did you initialize Harvest?' if project_id.nil?
25
+ end
26
+
27
+ def require_task!
28
+ if project_id.nil?
29
+ cli.abort 'No current/specified project. Did you initialize Harvest and pick a task?'
30
+ end
31
+ cli.abort 'No current/specified task. Did you pick a Harvest task?' if task_id.nil?
32
+ end
33
+
23
34
  def same_args_as_config?
24
35
  project_id == config.project_id && task_id == config.task_id
25
36
  end
26
37
 
27
38
  def print_project(project)
28
- cli.print_provider_command(
39
+ cli.print_scheme_argument(
29
40
  'harvest',
30
41
  project['id'],
31
42
  "#{project['client']['name']} > #{project['name']}"
@@ -33,20 +44,20 @@ module Abt
33
44
  end
34
45
 
35
46
  def print_task(project, task)
36
- cli.print_provider_command(
47
+ cli.print_scheme_argument(
37
48
  'harvest',
38
49
  "#{project['id']}/#{task['id']}",
39
50
  "#{project['name']} > #{task['name']}"
40
51
  )
41
52
  end
42
53
 
43
- def use_current_args
54
+ def use_current_path
44
55
  @project_id = config.project_id
45
56
  @task_id = config.task_id
46
57
  end
47
58
 
48
- def use_arg_str(arg_str)
49
- args = arg_str.to_s.split('/')
59
+ def use_path(path)
60
+ args = path.to_s.split('/')
50
61
  @project_id = args[0].to_s
51
62
  @project_id = nil if project_id.empty?
52
63
 
@@ -5,17 +5,28 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Clear < BaseCommand
8
- def self.command
9
- 'clear harvest'
8
+ def self.usage
9
+ 'abt clear harvest'
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Clear project/task for current git repository'
13
+ 'Clear harvest configuration'
14
14
  end
15
15
 
16
- def call
17
- cli.warn 'Clearing Harvest project configuration'
18
- config.clear_local
16
+ def self.flags
17
+ [
18
+ ['-g', '--global', 'Clear global instead of local harvest configuration (credentials etc.)'],
19
+ ['-a', '--all', 'Clear all harvest 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,17 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Current < BaseCommand
8
- def self.command
9
- 'current harvest[:<project-id>[/<task-id>]]'
8
+ def self.usage
9
+ 'abt current harvest[:<project-id>[/<task-id>]]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Get or set project and or task for current git repository'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
+ require_project!
18
+
17
19
  if same_args_as_config? || !config.local_available?
18
20
  show_current_configuration
19
21
  else
@@ -25,9 +27,7 @@ module Abt
25
27
  private
26
28
 
27
29
  def show_current_configuration
28
- if project_id.nil?
29
- cli.warn 'No project selected'
30
- elsif task_id.nil?
30
+ if task_id.nil?
31
31
  print_project(project)
32
32
  else
33
33
  print_task(project, task)
@@ -5,15 +5,15 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Init < BaseCommand
8
- def self.command
9
- 'init harvest'
8
+ def self.usage
9
+ 'abt init harvest'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Pick Harvest project for current git repository'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  cli.abort 'Must be run inside a git repository' unless config.local_available?
18
18
 
19
19
  projects # Load projects up front to make it obvious that searches are instant
@@ -31,14 +31,14 @@ module Abt
31
31
  cli.warn 'Select a project'
32
32
 
33
33
  loop do
34
- matches = matches_for_string cli.prompt('Enter search')
34
+ matches = matches_for_string cli.prompt.text('Enter search')
35
35
  if matches.empty?
36
36
  warn 'No matches'
37
37
  next
38
38
  end
39
39
 
40
40
  cli.warn 'Showing the 10 first matches' if matches.size > 10
41
- choice = cli.prompt_choice 'Select a project', matches[0...10], true
41
+ choice = cli.prompt.choice 'Select a project', matches[0...10], true
42
42
  break choice['project'] unless choice.nil?
43
43
  end
44
44
  end
@@ -5,24 +5,33 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Pick < BaseCommand
8
- def self.command
9
- 'pick harvest[:<project-id>]'
8
+ def self.usage
9
+ 'abt pick harvest[:<project-id>]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Pick task 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?
24
+ require_project!
18
25
 
19
26
  cli.warn project['name']
20
- task = cli.prompt_choice 'Select a task', tasks
27
+ task = cli.prompt.choice 'Select a task', tasks
28
+
29
+ print_task(project, task)
30
+
31
+ return if flags[:"dry-run"]
21
32
 
22
33
  config.project_id = project_id # We might have gotten the project ID as an argument
23
34
  config.task_id = task['id']
24
-
25
- print_task(project, task)
26
35
  end
27
36
 
28
37
  private
@@ -5,15 +5,15 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Projects < BaseCommand
8
- def self.command
9
- 'projects harvest'
8
+ def self.usage
9
+ 'abt projects harvest'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'List all available projects - useful for piping into grep etc.'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  projects.map do |project|
18
18
  print_project(project)
19
19
  end
@@ -5,21 +5,21 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Share < BaseCommand
8
- def self.command
9
- 'share harvest[:<project-id>[/<task-id>]]'
8
+ def self.usage
9
+ 'abt share harvest[:<project-id>[/<task-id>]]'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Print project/task config string'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  if project_id.nil?
18
18
  cli.warn 'No project selected'
19
19
  elsif task_id.nil?
20
- cli.print_provider_command('harvest', project_id)
20
+ cli.print_scheme_argument('harvest', project_id)
21
21
  else
22
- cli.print_provider_command('harvest', "#{project_id}/#{task_id}")
22
+ cli.print_scheme_argument('harvest', "#{project_id}/#{task_id}")
23
23
  end
24
24
  end
25
25
  end
@@ -1,56 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'track'
4
+
3
5
  module Abt
4
6
  module Providers
5
7
  module Harvest
6
8
  module Commands
7
- class Start < BaseCommand
8
- def self.command
9
- 'start harvest[:<project-id>/<task-id>]'
9
+ class Start < Track
10
+ def self.usage
11
+ 'abt start harvest[:<project-id>/<task-id>] [options]'
10
12
  end
11
13
 
12
14
  def self.description
13
- 'As track, but also lets the user override the current task and triggers `start` commands for other providers ' # rubocop:disable Layout/LineLength
14
- end
15
-
16
- def call
17
- track_output = call_track
18
- puts track_output
19
-
20
- use_arg_str(arg_str_from_track_output(track_output))
21
-
22
- maybe_override_current_task
23
- rescue Abt::HttpError::HttpError => e
24
- cli.warn e
25
- cli.abort 'Unable to start tracker'
26
- end
27
-
28
- private
29
-
30
- def arg_str_from_track_output(output)
31
- output = output.split(' # ').first
32
- output.split(':')[1]
33
- end
34
-
35
- def call_track
36
- input = StringIO.new(cli.args.join(' '))
37
- output = StringIO.new
38
- Abt::Cli.new(argv: ['track'], output: output, input: input).perform
39
-
40
- output_str = output.string.strip
41
- cli.abort 'No task provided' if output_str.empty?
42
- output_str
43
- end
44
-
45
- def maybe_override_current_task
46
- return if arg_str.nil?
47
- return if same_args_as_config?
48
- return unless config.local_available?
49
- return unless cli.prompt_boolean 'Set selected task as current?'
50
-
51
- input = StringIO.new("harvest:#{project_id}/#{task_id}")
52
- output = StringIO.new
53
- Abt::Cli.new(argv: ['current'], output: output, input: input).perform
15
+ 'Alias for: `abt track harvest`. Meant to used in combination other scheme arguments, e.g. `abt start harvest asana`'
54
16
  end
55
17
  end
56
18
  end