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,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
@@ -23,7 +23,7 @@ module Abt
23
23
 
24
24
  def projects
25
25
  @projects ||= begin
26
- cli.warn 'Fetching projects...'
26
+ warn 'Fetching projects...'
27
27
  project_assignments.map do |project_assignment|
28
28
  project_assignment['project'].merge('client' => project_assignment['client'])
29
29
  end
@@ -5,22 +5,18 @@ 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
- 'Print project/task config string'
13
+ 'Print project/task ARI'
14
14
  end
15
15
 
16
- def call
17
- if project_id.nil?
18
- cli.warn 'No project selected'
19
- elsif task_id.nil?
20
- cli.print_provider_command('harvest', project_id)
21
- else
22
- cli.print_provider_command('harvest', "#{project_id}/#{task_id}")
23
- end
16
+ def perform
17
+ require_project!
18
+
19
+ cli.print_ari('harvest', path)
24
20
  end
25
21
  end
26
22
  end
@@ -1,54 +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.string.strip
41
- end
42
-
43
- def maybe_override_current_task
44
- return if arg_str.nil?
45
- return if same_args_as_config?
46
- return unless config.local_available?
47
- return unless cli.prompt_boolean 'Set selected task as current?'
48
-
49
- input = StringIO.new("harvest:#{project_id}/#{task_id}")
50
- output = StringIO.new
51
- Abt::Cli.new(argv: ['current'], output: output, input: input).perform
15
+ 'Alias for: `abt track harvest`. Meant to used in combination with other ARIs, e.g. `abt start harvest asana`'
52
16
  end
53
17
  end
54
18
  end
@@ -5,30 +5,30 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Stop < BaseCommand
8
- def self.command
9
- 'stop harvest'
8
+ def self.usage
9
+ 'abt stop harvest'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'Stop running harvest tracker'
14
14
  end
15
15
 
16
- def call
17
- cli.abort 'No running time entry' if time_entry.nil?
16
+ def perform
17
+ abort 'No running time entry' if time_entry.nil?
18
18
 
19
19
  stop_time_entry
20
20
 
21
- cli.warn 'Harvest time entry stopped'
21
+ warn 'Harvest time entry stopped'
22
22
  print_task(project, task)
23
- rescue Abt::HttpError::HttpError => e
24
- cli.warn e
25
- cli.abort 'Unable to stop time entry'
26
23
  end
27
24
 
28
25
  private
29
26
 
30
27
  def stop_time_entry
31
28
  api.patch("time_entries/#{time_entry['id']}/stop")
29
+ rescue Abt::HttpError::HttpError => e
30
+ warn e
31
+ abort 'Unable to stop time entry'
32
32
  end
33
33
 
34
34
  def project
@@ -47,8 +47,8 @@ module Abt
47
47
  user_id: config.user_id
48
48
  ).first
49
49
  rescue Abt::HttpError::HttpError => e # rubocop:disable Layout/RescueEnsureAlignment
50
- cli.warn e
51
- cli.abort 'Unable to fetch running time entry'
50
+ warn e
51
+ abort 'Unable to fetch running time entry'
52
52
  end
53
53
  end
54
54
  end
@@ -5,15 +5,15 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Tasks < BaseCommand
8
- def self.command
9
- 'tasks harvest'
8
+ def self.usage
9
+ 'abt tasks harvest'
10
10
  end
11
11
 
12
12
  def self.description
13
13
  'List available tasks on project - useful for piping into grep etc.'
14
14
  end
15
15
 
16
- def call
16
+ def perform
17
17
  require_project!
18
18
 
19
19
  tasks.each do |task|
@@ -28,7 +28,10 @@ module Abt
28
28
  end
29
29
 
30
30
  def tasks
31
- @tasks ||= project_assignment['task_assignments'].map { |ta| ta['task'] }
31
+ @tasks ||= begin
32
+ warn 'Fetching tasks...'
33
+ project_assignment['task_assignments'].map { |ta| ta['task'] }
34
+ end
32
35
  end
33
36
 
34
37
  def project_assignment
@@ -5,22 +5,31 @@ module Abt
5
5
  module Harvest
6
6
  module Commands
7
7
  class Track < BaseCommand
8
- def self.command
9
- 'track harvest[:<project-id>/<task-id>]'
8
+ def self.usage
9
+ 'abt track harvest[:<project-id>/<task-id>] [options]'
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Start tracker for current or specified task. Add a relevant provider to link the time entry: E.g. `abt start harvest asana`' # rubocop:disable Layout/LineLength
13
+ 'Start tracker for current or specified task. Add a relevant ARI to link the time entry, e.g. `abt track harvest asana`'
14
14
  end
15
15
 
16
- def call
16
+ def self.flags
17
+ [
18
+ ['-s', '--set', 'Set specified task as current'],
19
+ ['-c', '--comment COMMENT', 'Override comment'],
20
+ ['-t', '--time HOURS', 'Set hours. Creates a stopped entry unless used with --running'],
21
+ ['-r', '--running', 'Used with --time, starts the created time entry']
22
+ ]
23
+ end
24
+
25
+ def perform
17
26
  require_task!
18
27
 
19
28
  print_task(created_time_entry['project'], created_time_entry['task'])
20
29
 
21
- cli.warn 'Tracker successfully started'
22
- rescue Abt::HttpError::HttpError => e
23
- cli.abort 'Invalid task'
30
+ maybe_override_current_task
31
+ rescue Abt::HttpError::HttpError => _e
32
+ abort 'Invalid task'
24
33
  end
25
34
 
26
35
  private
@@ -30,6 +39,19 @@ module Abt
30
39
  end
31
40
 
32
41
  def create_time_entry
42
+ body = time_entry_base_data
43
+ body.merge!(hours: flags[:time]) if flags.key? :time
44
+
45
+ result = api.post('time_entries', Oj.dump(body, mode: :json))
46
+
47
+ if flags.key?(:time) && flags[:running]
48
+ api.patch("time_entries/#{result['id']}/restart")
49
+ end
50
+
51
+ result
52
+ end
53
+
54
+ def time_entry_base_data
33
55
  body = {
34
56
  project_id: project_id,
35
57
  task_id: task_id,
@@ -38,33 +60,56 @@ module Abt
38
60
  }
39
61
 
40
62
  if external_link_data
63
+ warn <<~TXT
64
+ Linking to:
65
+ #{external_link_data[:notes]}
66
+ #{external_link_data[:external_reference][:permalink]}
67
+ TXT
41
68
  body.merge! external_link_data
42
69
  else
43
- cli.warn 'No external link provided'
44
- body[:notes] ||= cli.prompt('Fill in comment (optional)')
70
+ warn 'No external link provided'
45
71
  end
46
72
 
47
- api.post('time_entries', Oj.dump(body, mode: :json))
73
+ body[:notes] = flags[:comment] if flags.key?(:comment)
74
+ body[:notes] ||= cli.prompt.text('Fill in comment (optional)')
75
+ body
48
76
  end
49
77
 
50
78
  def external_link_data
51
79
  @external_link_data ||= begin
52
- input = StringIO.new(cli.args.join(' '))
53
- output = StringIO.new
54
- Abt::Cli.new(argv: ['harvest-time-entry-data'], output: output, input: input).perform
55
-
56
- lines = output.string.strip.lines
80
+ lines = call_harvest_time_entry_data_for_other_aris
57
81
 
58
- return if lines.empty?
82
+ if lines.empty?
83
+ nil
84
+ else
85
+ if lines.length > 1
86
+ abort('Got reference data from multiple scheme providers, only one is supported at a time')
87
+ end
59
88
 
60
- # TODO: Make user choose which reference to use by printing the urls
61
- if lines.length > 1
62
- cli.abort('Multiple providers had harvest reference data, only one is supported at a time') # rubocop:disable Layout/LineLength
89
+ Oj.load(lines.first, symbol_keys: true)
63
90
  end
64
-
65
- Oj.load(lines.first)
66
91
  end
67
92
  end
93
+
94
+ def call_harvest_time_entry_data_for_other_aris
95
+ other_aris = cli.aris - [ari]
96
+ return [] if other_aris.empty?
97
+
98
+ input = StringIO.new(other_aris.to_s)
99
+ output = StringIO.new
100
+ Abt::Cli.new(argv: ['harvest-time-entry-data'], output: output, input: input).perform
101
+
102
+ output.string.strip.lines
103
+ end
104
+
105
+ def maybe_override_current_task
106
+ return unless flags[:set]
107
+ return if path == config.path
108
+ return unless config.local_available?
109
+
110
+ config.path = path
111
+ warn 'Current task updated'
112
+ end
68
113
  end
69
114
  end
70
115
  end
@@ -8,52 +8,32 @@ module Abt
8
8
 
9
9
  def initialize(cli:)
10
10
  @cli = cli
11
- @git = GitConfig.new(namespace: 'abt.harvest')
12
11
  end
13
12
 
14
13
  def local_available?
15
- GitConfig.local_available?
14
+ git.available?
16
15
  end
17
16
 
18
- def project_id
19
- local_available? ? git['projectId'] : nil
17
+ def path
18
+ Path.new(local_available? && git['path'] || '')
20
19
  end
21
20
 
22
- def task_id
23
- local_available? ? git['taskId'] : nil
21
+ def path=(new_path)
22
+ git['path'] = new_path
24
23
  end
25
24
 
26
- def project_id=(value)
27
- value = value.to_s unless value.nil?
28
- return if project_id == value
29
-
30
- clear_local
31
- git['projectId'] = value
25
+ def clear_local(verbose: true)
26
+ git.clear(output: verbose ? cli.err_output : nil)
32
27
  end
33
28
 
34
- def task_id=(value)
35
- value = value.to_s unless value.nil?
36
- git['taskId'] = value
37
- end
38
-
39
- def clear_local
40
- cli.abort 'No local configuration was found' unless local_available?
41
-
42
- git['projectId'] = nil
43
- git['taskId'] = nil
44
- end
45
-
46
- def clear_global
47
- git.global.keys.each do |key|
48
- cli.puts 'Deleting configuration: ' + key
49
- git.global[key] = nil
50
- end
29
+ def clear_global(verbose: true)
30
+ git_global.clear(output: verbose ? cli.err_output : nil)
51
31
  end
52
32
 
53
33
  def access_token
54
- return git.global['accessToken'] unless git.global['accessToken'].nil?
34
+ return git_global['accessToken'] unless git_global['accessToken'].nil?
55
35
 
56
- git.global['accessToken'] = cli.prompt([
36
+ git_global['accessToken'] = cli.prompt.text([
57
37
  'Please provide your personal access token for Harvest.',
58
38
  'If you don\'t have one, create one here: https://id.getharvest.com/developers',
59
39
  '',
@@ -62,9 +42,9 @@ module Abt
62
42
  end
63
43
 
64
44
  def account_id
65
- return git.global['accountId'] unless git.global['accountId'].nil?
45
+ return git_global['accountId'] unless git_global['accountId'].nil?
66
46
 
67
- git.global['accountId'] = cli.prompt([
47
+ git_global['accountId'] = cli.prompt.text([
68
48
  'Please provide harvest account id.',
69
49
  'This information is shown next to your generated access token',
70
50
  '',
@@ -73,18 +53,23 @@ module Abt
73
53
  end
74
54
 
75
55
  def user_id
76
- return git.global['userId'] unless git.global['userId'].nil?
56
+ return git_global['userId'] unless git_global['userId'].nil?
77
57
 
78
- git.global['userId'] = api.get('users/me')['id'].to_s
58
+ git_global['userId'] = api.get('users/me')['id'].to_s
79
59
  end
80
60
 
81
61
  private
82
62
 
83
- attr_reader :git
63
+ def git
64
+ @git ||= GitConfig.new('local', 'abt.harvest')
65
+ end
66
+
67
+ def git_global
68
+ @git_global ||= GitConfig.new('global', 'abt.harvest')
69
+ end
84
70
 
85
71
  def api
86
- @api ||=
87
- Abt::Providers::Harvest::Api.new(access_token: access_token, account_id: account_id)
72
+ @api ||= Api.new(access_token: access_token, account_id: account_id)
88
73
  end
89
74
  end
90
75
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Harvest
6
+ class Path < String
7
+ PATH_REGEX = %r{^(?<project_id>\d+)?(/(?<task_id>\d+))?$}.freeze
8
+
9
+ def self.from_ids(project_id = nil, task_id = nil)
10
+ path = project_id ? [project_id, *task_id].join('/') : ''
11
+ new path
12
+ end
13
+
14
+ def initialize(path = '')
15
+ raise Abt::Cli::Abort, "Invalid path: #{path}" unless path =~ PATH_REGEX
16
+
17
+ super
18
+ end
19
+
20
+ def project_id
21
+ match[:project_id]
22
+ end
23
+
24
+ def task_id
25
+ match[:task_id]
26
+ end
27
+
28
+ private
29
+
30
+ def match
31
+ @match ||= PATH_REGEX.match(self)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end