abt-cli 0.0.10 → 0.0.15

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +0 -6
  3. data/lib/abt.rb +8 -0
  4. data/lib/abt/cli.rb +27 -11
  5. data/lib/abt/cli/prompt.rb +124 -0
  6. data/lib/abt/git_config.rb +28 -11
  7. data/lib/abt/helpers.rb +1 -1
  8. data/lib/abt/providers/asana/base_command.rb +11 -0
  9. data/lib/abt/providers/asana/commands/add.rb +75 -0
  10. data/lib/abt/providers/asana/commands/branch-name.rb +44 -0
  11. data/lib/abt/providers/asana/commands/current.rb +3 -3
  12. data/lib/abt/providers/asana/commands/finalize.rb +1 -1
  13. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +3 -4
  14. data/lib/abt/providers/asana/commands/init.rb +7 -2
  15. data/lib/abt/providers/asana/commands/pick.rb +3 -2
  16. data/lib/abt/providers/asana/commands/share.rb +3 -3
  17. data/lib/abt/providers/asana/commands/start.rb +3 -3
  18. data/lib/abt/providers/asana/commands/tasks.rb +2 -0
  19. data/lib/abt/providers/asana/configuration.rb +7 -5
  20. data/lib/abt/providers/devops.rb +19 -0
  21. data/lib/abt/providers/devops/api.rb +95 -0
  22. data/lib/abt/providers/devops/base_command.rb +98 -0
  23. data/lib/abt/providers/devops/commands/boards.rb +34 -0
  24. data/lib/abt/providers/devops/commands/branch-name.rb +45 -0
  25. data/lib/abt/providers/devops/commands/clear.rb +24 -0
  26. data/lib/abt/providers/devops/commands/clear_global.rb +24 -0
  27. data/lib/abt/providers/devops/commands/current.rb +93 -0
  28. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +53 -0
  29. data/lib/abt/providers/devops/commands/init.rb +72 -0
  30. data/lib/abt/providers/devops/commands/pick.rb +78 -0
  31. data/lib/abt/providers/devops/commands/share.rb +26 -0
  32. data/lib/abt/providers/devops/commands/work-items.rb +46 -0
  33. data/lib/abt/providers/devops/configuration.rb +110 -0
  34. data/lib/abt/providers/git.rb +19 -0
  35. data/lib/abt/providers/git/commands/branch.rb +80 -0
  36. data/lib/abt/providers/harvest/base_command.rb +11 -0
  37. data/lib/abt/providers/harvest/commands/current.rb +3 -3
  38. data/lib/abt/providers/harvest/commands/init.rb +2 -2
  39. data/lib/abt/providers/harvest/commands/pick.rb +2 -1
  40. data/lib/abt/providers/harvest/commands/start.rb +2 -4
  41. data/lib/abt/providers/harvest/commands/tasks.rb +2 -0
  42. data/lib/abt/providers/harvest/commands/track.rb +2 -3
  43. data/lib/abt/providers/harvest/configuration.rb +6 -5
  44. data/lib/abt/version.rb +1 -1
  45. metadata +21 -4
  46. data/lib/abt/cli/dialogs.rb +0 -86
  47. data/lib/abt/cli/io.rb +0 -23
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ class Configuration
7
+ attr_accessor :cli
8
+
9
+ def initialize(cli:)
10
+ @cli = cli
11
+ @git = GitConfig.new(namespace: 'abt.devops')
12
+ end
13
+
14
+ def local_available?
15
+ GitConfig.local_available?
16
+ end
17
+
18
+ def organization_name
19
+ local_available? ? git['organizationName'] : nil
20
+ end
21
+
22
+ def project_name
23
+ local_available? ? git['projectName'] : nil
24
+ end
25
+
26
+ def board_id
27
+ local_available? ? git['boardId'] : nil
28
+ end
29
+
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
74
+ end
75
+
76
+ def username_for_organization(organization_name)
77
+ username_key = "organizations.#{organization_name}.username"
78
+
79
+ return git.global[username_key] unless git.global[username_key].nil?
80
+
81
+ git.global[username_key] = cli.prompt.text([
82
+ "Please provide your username for the DevOps organization (#{organization_name}).",
83
+ '',
84
+ 'Enter username'
85
+ ].join("\n"))
86
+ end
87
+
88
+ def access_token_for_organization(organization_name)
89
+ access_token_key = "organizations.#{organization_name}.accessToken"
90
+
91
+ return git.global[access_token_key] unless git.global[access_token_key].nil?
92
+
93
+ git.global[access_token_key] = cli.prompt.text([
94
+ "Please provide your personal access token for the DevOps organization (#{organization_name}).",
95
+ '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
+ '',
97
+ 'The token MUST have "Read" permission for Work Items',
98
+ 'Future features will likely require "Write" or "Manage"',
99
+ '',
100
+ 'Enter access token'
101
+ ].join("\n"))
102
+ end
103
+
104
+ private
105
+
106
+ attr_reader :git
107
+ end
108
+ end
109
+ end
110
+ end
@@ -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,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Git
6
+ module Commands
7
+ class Branch
8
+ attr_reader :cli
9
+
10
+ def self.command
11
+ 'branch git <provider>'
12
+ end
13
+
14
+ def self.description
15
+ 'Switch branch. Uses a compatible provider to generate the branch-name: E.g. `abt branch git asana`'
16
+ end
17
+
18
+ def initialize(cli:, **)
19
+ @cli = cli
20
+ end
21
+
22
+ def call
23
+ create_and_switch unless switch
24
+ cli.warn "Switched to #{branch_name}"
25
+ end
26
+
27
+ private
28
+
29
+ def switch
30
+ success = false
31
+ Open3.popen3("git switch #{branch_name}") do |_i, _o, _error_output, thread|
32
+ success = thread.value.success?
33
+ end
34
+ success
35
+ end
36
+
37
+ def create_and_switch
38
+ cli.warn "No such branch: #{branch_name}"
39
+ cli.abort('Aborting') unless cli.prompt.boolean 'Create branch?'
40
+
41
+ Open3.popen3("git switch -c #{branch_name}") do |_i, _o, _e, thread|
42
+ thread.value
43
+ end
44
+ end
45
+
46
+ def branch_name # rubocop:disable Metrics/MethodLength
47
+ @branch_name ||= begin
48
+ if branch_names_from_providers.empty?
49
+ cli.abort [
50
+ 'None of the specified providers responded to `branch-name`.',
51
+ 'Did you add compatible provider? e.g.:',
52
+ ' abt branch git asana',
53
+ ' abt branch git devops'
54
+ ].join("\n")
55
+ end
56
+
57
+ if branch_names_from_providers.length > 1
58
+ cli.abort [
59
+ 'Got branch names from multiple providers, only one is supported',
60
+ 'Branch names where:',
61
+ *branch_names_from_providers.map { |name| " #{name}" }
62
+ ].join("\n")
63
+ end
64
+
65
+ branch_names_from_providers.first
66
+ end
67
+ end
68
+
69
+ def branch_names_from_providers
70
+ input = StringIO.new(cli.args.join(' '))
71
+ output = StringIO.new
72
+ Abt::Cli.new(argv: ['branch-name'], output: output, input: input).perform
73
+
74
+ output.string.lines.map(&:strip).compact
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -20,6 +20,17 @@ module Abt
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
@@ -14,6 +14,8 @@ module Abt
14
14
  end
15
15
 
16
16
  def call
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)
@@ -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
@@ -15,9 +15,10 @@ module Abt
15
15
 
16
16
  def call
17
17
  cli.abort 'Must be run inside a git repository' unless config.local_available?
18
+ require_project!
18
19
 
19
20
  cli.warn project['name']
20
- task = cli.prompt_choice 'Select a task', tasks
21
+ task = cli.prompt.choice 'Select a task', tasks
21
22
 
22
23
  config.project_id = project_id # We might have gotten the project ID as an argument
23
24
  config.task_id = task['id']
@@ -37,16 +37,14 @@ module Abt
37
37
  output = StringIO.new
38
38
  Abt::Cli.new(argv: ['track'], output: output, input: input).perform
39
39
 
40
- output_str = output.string.strip
41
- cli.abort 'No task provided' if output_str.empty?
42
- output_str
40
+ output.string.strip
43
41
  end
44
42
 
45
43
  def maybe_override_current_task
46
44
  return if arg_str.nil?
47
45
  return if same_args_as_config?
48
46
  return unless config.local_available?
49
- return unless cli.prompt_boolean 'Set selected task as current?'
47
+ return unless cli.prompt.boolean 'Set selected task as current?'
50
48
 
51
49
  input = StringIO.new("harvest:#{project_id}/#{task_id}")
52
50
  output = StringIO.new
@@ -14,6 +14,8 @@ module Abt
14
14
  end
15
15
 
16
16
  def call
17
+ require_project!
18
+
17
19
  tasks.each do |task|
18
20
  print_task(project, task)
19
21
  end
@@ -14,8 +14,7 @@ module Abt
14
14
  end
15
15
 
16
16
  def call
17
- abort 'No current/provided task' if task_id.nil?
18
- cli.abort('No task selected') if task_id.nil?
17
+ require_task!
19
18
 
20
19
  print_task(created_time_entry['project'], created_time_entry['task'])
21
20
 
@@ -42,7 +41,7 @@ module Abt
42
41
  body.merge! external_link_data
43
42
  else
44
43
  cli.warn 'No external link provided'
45
- body[:notes] ||= cli.prompt('Fill in comment (optional)')
44
+ body[:notes] ||= cli.prompt.text('Fill in comment (optional)')
46
45
  end
47
46
 
48
47
  api.post('time_entries', Oj.dump(body, mode: :json))
@@ -44,15 +44,16 @@ module Abt
44
44
  end
45
45
 
46
46
  def clear_global
47
- git.global['userId'] = nil
48
- git.global['accountId'] = nil
49
- git.global['accessToken'] = nil
47
+ git.global.keys.each do |key|
48
+ cli.puts 'Deleting configuration: ' + key
49
+ git.global[key] = nil
50
+ end
50
51
  end
51
52
 
52
53
  def access_token
53
54
  return git.global['accessToken'] unless git.global['accessToken'].nil?
54
55
 
55
- git.global['accessToken'] = cli.prompt([
56
+ git.global['accessToken'] = cli.prompt.text([
56
57
  'Please provide your personal access token for Harvest.',
57
58
  'If you don\'t have one, create one here: https://id.getharvest.com/developers',
58
59
  '',
@@ -63,7 +64,7 @@ module Abt
63
64
  def account_id
64
65
  return git.global['accountId'] unless git.global['accountId'].nil?
65
66
 
66
- git.global['accountId'] = cli.prompt([
67
+ git.global['accountId'] = cli.prompt.text([
67
68
  'Please provide harvest account id.',
68
69
  'This information is shown next to your generated access token',
69
70
  '',
data/lib/abt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Abt
4
- VERSION = '0.0.10'
4
+ VERSION = '0.0.15'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abt-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesper Sørensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-27 00:00:00.000000000 Z
11
+ date: 2021-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-inflector
@@ -76,8 +76,7 @@ extra_rdoc_files: []
76
76
  files:
77
77
  - "./lib/abt.rb"
78
78
  - "./lib/abt/cli.rb"
79
- - "./lib/abt/cli/dialogs.rb"
80
- - "./lib/abt/cli/io.rb"
79
+ - "./lib/abt/cli/prompt.rb"
81
80
  - "./lib/abt/docs.rb"
82
81
  - "./lib/abt/docs/cli.rb"
83
82
  - "./lib/abt/docs/markdown.rb"
@@ -88,6 +87,8 @@ files:
88
87
  - "./lib/abt/providers/asana.rb"
89
88
  - "./lib/abt/providers/asana/api.rb"
90
89
  - "./lib/abt/providers/asana/base_command.rb"
90
+ - "./lib/abt/providers/asana/commands/add.rb"
91
+ - "./lib/abt/providers/asana/commands/branch-name.rb"
91
92
  - "./lib/abt/providers/asana/commands/clear.rb"
92
93
  - "./lib/abt/providers/asana/commands/clear_global.rb"
93
94
  - "./lib/abt/providers/asana/commands/current.rb"
@@ -100,6 +101,22 @@ files:
100
101
  - "./lib/abt/providers/asana/commands/start.rb"
101
102
  - "./lib/abt/providers/asana/commands/tasks.rb"
102
103
  - "./lib/abt/providers/asana/configuration.rb"
104
+ - "./lib/abt/providers/devops.rb"
105
+ - "./lib/abt/providers/devops/api.rb"
106
+ - "./lib/abt/providers/devops/base_command.rb"
107
+ - "./lib/abt/providers/devops/commands/boards.rb"
108
+ - "./lib/abt/providers/devops/commands/branch-name.rb"
109
+ - "./lib/abt/providers/devops/commands/clear.rb"
110
+ - "./lib/abt/providers/devops/commands/clear_global.rb"
111
+ - "./lib/abt/providers/devops/commands/current.rb"
112
+ - "./lib/abt/providers/devops/commands/harvest_time_entry_data.rb"
113
+ - "./lib/abt/providers/devops/commands/init.rb"
114
+ - "./lib/abt/providers/devops/commands/pick.rb"
115
+ - "./lib/abt/providers/devops/commands/share.rb"
116
+ - "./lib/abt/providers/devops/commands/work-items.rb"
117
+ - "./lib/abt/providers/devops/configuration.rb"
118
+ - "./lib/abt/providers/git.rb"
119
+ - "./lib/abt/providers/git/commands/branch.rb"
103
120
  - "./lib/abt/providers/harvest.rb"
104
121
  - "./lib/abt/providers/harvest/api.rb"
105
122
  - "./lib/abt/providers/harvest/base_command.rb"
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- class Cli
5
- module Dialogs
6
- def prompt(question)
7
- err_output.print "#{question}: "
8
- read_user_input.strip
9
- end
10
-
11
- def prompt_boolean(text)
12
- warn text
13
-
14
- loop do
15
- err_output.print '(y / n): '
16
-
17
- case read_user_input.strip
18
- when 'y', 'Y' then return true
19
- when 'n', 'N' then return false
20
- else
21
- warn 'Invalid choice'
22
- next
23
- end
24
- end
25
- end
26
-
27
- def prompt_choice(text, options, allow_back_option = false)
28
- warn "#{text}:"
29
-
30
- if options.length.zero?
31
- abort 'No available options' unless allow_back_option
32
-
33
- warn 'No available options'
34
- return nil
35
- end
36
-
37
- print_options(options)
38
- select_options(options, allow_back_option)
39
- end
40
-
41
- private
42
-
43
- def print_options(options)
44
- options.each_with_index do |option, index|
45
- warn "(#{index + 1}) #{option['name']}"
46
- end
47
- end
48
-
49
- def select_options(options, allow_back_option)
50
- loop do
51
- number = read_option_number(options.length, allow_back_option)
52
- if number.nil?
53
- return nil if allow_back_option
54
-
55
- next
56
- end
57
-
58
- option = options[number - 1]
59
-
60
- warn "Selected: (#{number}) #{option['name']}"
61
- return option
62
- end
63
- end
64
-
65
- def read_option_number(options_length, allow_back_option)
66
- err_output.print "(1-#{options_length}#{allow_back_option ? ', q: back' : ''}): "
67
-
68
- input = read_user_input
69
-
70
- return nil if allow_back_option && input == 'q'
71
-
72
- option_number = input.to_i
73
- if option_number <= 0 || option_number > options_length
74
- warn 'Invalid selection'
75
- return nil
76
- end
77
-
78
- option_number
79
- end
80
-
81
- def read_user_input
82
- open('/dev/tty', &:gets).strip
83
- end
84
- end
85
- end
86
- end