abt-cli 0.0.17 → 0.0.22

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +3 -3
  3. data/lib/abt.rb +6 -6
  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 +58 -59
  8. data/lib/abt/cli/arguments_parser.rb +8 -27
  9. data/lib/abt/cli/global_commands.rb +23 -0
  10. data/lib/abt/cli/global_commands/commands.rb +23 -0
  11. data/lib/abt/cli/global_commands/examples.rb +23 -0
  12. data/lib/abt/cli/global_commands/help.rb +23 -0
  13. data/lib/abt/cli/global_commands/readme.rb +23 -0
  14. data/lib/abt/cli/global_commands/share.rb +36 -0
  15. data/lib/abt/cli/global_commands/version.rb +23 -0
  16. data/lib/abt/cli/prompt.rb +52 -20
  17. data/lib/abt/docs.rb +48 -25
  18. data/lib/abt/docs/cli.rb +7 -7
  19. data/lib/abt/docs/markdown.rb +13 -12
  20. data/lib/abt/git_config.rb +21 -39
  21. data/lib/abt/providers/asana/api.rb +9 -9
  22. data/lib/abt/providers/asana/base_command.rb +16 -38
  23. data/lib/abt/providers/asana/commands/add.rb +18 -15
  24. data/lib/abt/providers/asana/commands/branch_name.rb +13 -8
  25. data/lib/abt/providers/asana/commands/clear.rb +8 -7
  26. data/lib/abt/providers/asana/commands/current.rb +23 -38
  27. data/lib/abt/providers/asana/commands/finalize.rb +11 -16
  28. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +14 -9
  29. data/lib/abt/providers/asana/commands/init.rb +8 -41
  30. data/lib/abt/providers/asana/commands/pick.rb +22 -17
  31. data/lib/abt/providers/asana/commands/projects.rb +5 -5
  32. data/lib/abt/providers/asana/commands/share.rb +6 -8
  33. data/lib/abt/providers/asana/commands/start.rb +26 -23
  34. data/lib/abt/providers/asana/commands/tasks.rb +6 -5
  35. data/lib/abt/providers/asana/configuration.rb +34 -40
  36. data/lib/abt/providers/asana/path.rb +36 -0
  37. data/lib/abt/providers/devops/api.rb +23 -11
  38. data/lib/abt/providers/devops/base_command.rb +18 -43
  39. data/lib/abt/providers/devops/commands/boards.rb +5 -7
  40. data/lib/abt/providers/devops/commands/branch_name.rb +14 -10
  41. data/lib/abt/providers/devops/commands/clear.rb +8 -7
  42. data/lib/abt/providers/devops/commands/current.rb +25 -49
  43. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +20 -12
  44. data/lib/abt/providers/devops/commands/init.rb +29 -25
  45. data/lib/abt/providers/devops/commands/pick.rb +11 -18
  46. data/lib/abt/providers/devops/commands/share.rb +7 -6
  47. data/lib/abt/providers/devops/commands/{work-items.rb → work_items.rb} +3 -3
  48. data/lib/abt/providers/devops/configuration.rb +31 -56
  49. data/lib/abt/providers/devops/path.rb +51 -0
  50. data/lib/abt/providers/git/commands/branch.rb +29 -25
  51. data/lib/abt/providers/harvest/api.rb +8 -8
  52. data/lib/abt/providers/harvest/base_command.rb +18 -38
  53. data/lib/abt/providers/harvest/commands/clear.rb +8 -7
  54. data/lib/abt/providers/harvest/commands/current.rb +28 -35
  55. data/lib/abt/providers/harvest/commands/init.rb +10 -39
  56. data/lib/abt/providers/harvest/commands/pick.rb +11 -12
  57. data/lib/abt/providers/harvest/commands/projects.rb +5 -5
  58. data/lib/abt/providers/harvest/commands/share.rb +6 -8
  59. data/lib/abt/providers/harvest/commands/start.rb +5 -3
  60. data/lib/abt/providers/harvest/commands/stop.rb +13 -13
  61. data/lib/abt/providers/harvest/commands/tasks.rb +9 -6
  62. data/lib/abt/providers/harvest/commands/track.rb +40 -32
  63. data/lib/abt/providers/harvest/configuration.rb +28 -37
  64. data/lib/abt/providers/harvest/path.rb +36 -0
  65. data/lib/abt/version.rb +1 -1
  66. metadata +18 -6
  67. data/lib/abt/cli/base_command.rb +0 -61
@@ -6,52 +6,51 @@ module Abt
6
6
  module Commands
7
7
  class Pick < BaseCommand
8
8
  def self.usage
9
- 'abt pick harvest[:<project-id>]'
9
+ "abt pick harvest[:<project-id>]"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Pick task for current git repository'
13
+ "Pick task for current git repository"
14
14
  end
15
15
 
16
16
  def self.flags
17
17
  [
18
- ['-d', '--dry-run', 'Keep existing configuration']
18
+ ["-d", "--dry-run", "Keep existing configuration"]
19
19
  ]
20
20
  end
21
21
 
22
22
  def perform
23
- cli.abort 'Must be run inside a git repository' unless config.local_available?
23
+ abort("Must be run inside a git repository") unless config.local_available?
24
24
  require_project!
25
25
 
26
- cli.warn project['name']
27
- task = cli.prompt.choice 'Select a task', tasks
26
+ warn(project["name"])
27
+ task = cli.prompt.choice("Select a task", tasks)
28
28
 
29
29
  print_task(project, task)
30
30
 
31
31
  return if flags[:"dry-run"]
32
32
 
33
- config.project_id = project_id # We might have gotten the project ID as an argument
34
- config.task_id = task['id']
33
+ config.path = Path.from_ids(project_id, task["id"])
35
34
  end
36
35
 
37
36
  private
38
37
 
39
38
  def project
40
- project_assignment['project']
39
+ project_assignment["project"]
41
40
  end
42
41
 
43
42
  def tasks
44
- @tasks ||= project_assignment['task_assignments'].map { |ta| ta['task'] }
43
+ @tasks ||= project_assignment["task_assignments"].map { |ta| ta["task"] }
45
44
  end
46
45
 
47
46
  def project_assignment
48
47
  @project_assignment ||= begin
49
- project_assignments.find { |pa| pa['project']['id'].to_s == project_id }
48
+ project_assignments.find { |pa| pa["project"]["id"].to_s == project_id }
50
49
  end
51
50
  end
52
51
 
53
52
  def project_assignments
54
- @project_assignments ||= api.get_paged('users/me/project_assignments')
53
+ @project_assignments ||= api.get_paged("users/me/project_assignments")
55
54
  end
56
55
  end
57
56
  end
@@ -6,11 +6,11 @@ module Abt
6
6
  module Commands
7
7
  class Projects < BaseCommand
8
8
  def self.usage
9
- 'abt projects harvest'
9
+ "abt projects harvest"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'List all available projects - useful for piping into grep etc.'
13
+ "List all available projects - useful for piping into grep etc."
14
14
  end
15
15
 
16
16
  def perform
@@ -23,15 +23,15 @@ 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
- project_assignment['project'].merge('client' => project_assignment['client'])
28
+ project_assignment["project"].merge("client" => project_assignment["client"])
29
29
  end
30
30
  end
31
31
  end
32
32
 
33
33
  def project_assignments
34
- @project_assignments ||= api.get_paged('users/me/project_assignments')
34
+ @project_assignments ||= api.get_paged("users/me/project_assignments")
35
35
  end
36
36
  end
37
37
  end
@@ -6,20 +6,18 @@ module Abt
6
6
  module Commands
7
7
  class Share < BaseCommand
8
8
  def self.usage
9
- 'abt share harvest[:<project-id>[/<task-id>]]'
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
16
  def perform
17
- if project_id.nil?
18
- cli.warn 'No project selected'
19
- elsif task_id.nil?
20
- cli.print_scheme_argument('harvest', project_id)
21
- else
22
- cli.print_scheme_argument('harvest', "#{project_id}/#{task_id}")
17
+ if path != ""
18
+ cli.print_ari("harvest", path)
19
+ elsif cli.output.isatty
20
+ warn("No configuration for project. Did you initialize Harvest?")
23
21
  end
24
22
  end
25
23
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'track'
3
+ require_relative "track"
4
4
 
5
5
  module Abt
6
6
  module Providers
@@ -8,11 +8,13 @@ module Abt
8
8
  module Commands
9
9
  class Start < Track
10
10
  def self.usage
11
- 'abt start harvest[:<project-id>/<task-id>] [options]'
11
+ "abt start harvest[:<project-id>/<task-id>] [options]"
12
12
  end
13
13
 
14
14
  def self.description
15
- 'Alias for: `abt track harvest`. Meant to used in combination with other scheme arguments, e.g. `abt start harvest asana`'
15
+ <<~TXT
16
+ Alias for: `abt track harvest`. Meant to used in combination with other ARIs, e.g. `abt start harvest asana`
17
+ TXT
16
18
  end
17
19
  end
18
20
  end
@@ -6,49 +6,49 @@ module Abt
6
6
  module Commands
7
7
  class Stop < BaseCommand
8
8
  def self.usage
9
- 'abt stop harvest'
9
+ "abt stop harvest"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Stop running harvest tracker'
13
+ "Stop running harvest tracker"
14
14
  end
15
15
 
16
16
  def perform
17
- cli.abort 'No running time entry' if time_entry.nil?
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
35
- time_entry['project']
35
+ time_entry["project"]
36
36
  end
37
37
 
38
38
  def task
39
- time_entry['task']
39
+ time_entry["task"]
40
40
  end
41
41
 
42
42
  def time_entry
43
43
  @time_entry ||= begin
44
44
  api.get_paged(
45
- 'time_entries',
45
+ "time_entries",
46
46
  is_running: true,
47
47
  user_id: config.user_id
48
48
  ).first
49
- rescue Abt::HttpError::HttpError => e # rubocop:disable Layout/RescueEnsureAlignment
50
- cli.warn e
51
- cli.abort 'Unable to fetch running time entry'
49
+ rescue Abt::HttpError::HttpError => e
50
+ warn(e)
51
+ abort("Unable to fetch running time entry")
52
52
  end
53
53
  end
54
54
  end
@@ -6,11 +6,11 @@ module Abt
6
6
  module Commands
7
7
  class Tasks < BaseCommand
8
8
  def self.usage
9
- 'abt tasks harvest'
9
+ "abt tasks harvest"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'List available tasks on project - useful for piping into grep etc.'
13
+ "List available tasks on project - useful for piping into grep etc."
14
14
  end
15
15
 
16
16
  def perform
@@ -24,21 +24,24 @@ module Abt
24
24
  private
25
25
 
26
26
  def project
27
- project_assignment['project']
27
+ project_assignment["project"]
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
35
38
  @project_assignment ||= begin
36
- project_assignments.find { |pa| pa['project']['id'].to_s == project_id }
39
+ project_assignments.find { |pa| pa["project"]["id"].to_s == project_id }
37
40
  end
38
41
  end
39
42
 
40
43
  def project_assignments
41
- @project_assignments ||= api.get_paged('users/me/project_assignments')
44
+ @project_assignments ||= api.get_paged("users/me/project_assignments")
42
45
  end
43
46
  end
44
47
  end
@@ -6,30 +6,33 @@ module Abt
6
6
  module Commands
7
7
  class Track < BaseCommand
8
8
  def self.usage
9
- 'abt track harvest[:<project-id>/<task-id>] [options]'
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 scheme argument to link the time entry, e.g. `abt track harvest asana`'
13
+ <<~TXT
14
+ Start tracker for current or specified task. Add a relevant ARI to link the time entry, e.g. `abt track harvest asana`
15
+ TXT
14
16
  end
15
17
 
16
18
  def self.flags
17
19
  [
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']
20
+ ["-s", "--set", "Set specified task as current"],
21
+ ["-c", "--comment COMMENT", "Override comment"],
22
+ ["-t", "--time HOURS",
23
+ "Set hours. Creates a stopped entry unless used with --running"],
24
+ ["-r", "--running", "Used with --time, starts the created time entry"]
22
25
  ]
23
26
  end
24
27
 
25
28
  def perform
26
29
  require_task!
27
30
 
28
- print_task(created_time_entry['project'], created_time_entry['task'])
31
+ print_task(created_time_entry["project"], created_time_entry["task"])
29
32
 
30
33
  maybe_override_current_task
31
34
  rescue Abt::HttpError::HttpError => _e
32
- cli.abort 'Invalid task'
35
+ abort("Invalid task")
33
36
  end
34
37
 
35
38
  private
@@ -40,13 +43,11 @@ module Abt
40
43
 
41
44
  def create_time_entry
42
45
  body = time_entry_base_data
43
- body.merge!(hours: flags[:time]) if flags.key? :time
46
+ body[:hours] = flags[:time] if flags.key?(:time)
44
47
 
45
- result = api.post('time_entries', Oj.dump(body, mode: :json))
48
+ result = api.post("time_entries", Oj.dump(body, mode: :json))
46
49
 
47
- if flags.key?(:time) && flags[:running]
48
- api.patch("time_entries/#{result['id']}/restart")
49
- end
50
+ api.patch("time_entries/#{result['id']}/restart") if flags.key?(:time) && flags[:running]
50
51
 
51
52
  result
52
53
  end
@@ -60,48 +61,55 @@ module Abt
60
61
  }
61
62
 
62
63
  if external_link_data
63
- cli.warn <<~TXT
64
+ warn(<<~TXT)
64
65
  Linking to:
65
66
  #{external_link_data[:notes]}
66
67
  #{external_link_data[:external_reference][:permalink]}
67
68
  TXT
68
- body.merge! external_link_data
69
+ body.merge!(external_link_data)
69
70
  else
70
- cli.warn 'No external link provided'
71
+ warn("No external link provided")
71
72
  end
72
73
 
73
74
  body[:notes] = flags[:comment] if flags.key?(:comment)
74
- body[:notes] ||= cli.prompt.text('Fill in comment (optional)')
75
+ body[:notes] ||= cli.prompt.text("Fill in comment (optional)")
75
76
  body
76
77
  end
77
78
 
78
79
  def external_link_data
79
80
  @external_link_data ||= begin
80
- input = StringIO.new(cli.scheme_arguments.to_s)
81
- output = StringIO.new
82
- Abt::Cli.new(argv: ['harvest-time-entry-data'], output: output, input: input).perform
83
-
84
- lines = output.string.strip.lines
81
+ lines = call_harvest_time_entry_data_for_other_aris
85
82
 
86
- return if lines.empty?
83
+ if lines.empty?
84
+ nil
85
+ else
86
+ if lines.length > 1
87
+ abort("Got reference data from multiple scheme providers, only one is supported at a time")
88
+ end
87
89
 
88
- # TODO: Make user choose which reference to use by printing the urls
89
- if lines.length > 1
90
- cli.abort('Got reference data from multiple scheme providers, only one is supported at a time')
90
+ Oj.load(lines.first, symbol_keys: true)
91
91
  end
92
-
93
- Oj.load(lines.first, symbol_keys: true)
94
92
  end
95
93
  end
96
94
 
95
+ def call_harvest_time_entry_data_for_other_aris
96
+ other_aris = cli.aris - [ari]
97
+ return [] if other_aris.empty?
98
+
99
+ input = StringIO.new(other_aris.to_s)
100
+ output = StringIO.new
101
+ Abt::Cli.new(argv: ["harvest-time-entry-data"], output: output, input: input).perform
102
+
103
+ output.string.strip.lines
104
+ end
105
+
97
106
  def maybe_override_current_task
98
107
  return unless flags[:set]
99
- return if same_args_as_config?
108
+ return if path == config.path
100
109
  return unless config.local_available?
101
110
 
102
- input = StringIO.new("harvest:#{project_id}/#{task_id}")
103
- output = StringIO.new
104
- Abt::Cli.new(argv: ['current'], output: output, input: input).perform
111
+ config.path = path
112
+ warn("Current task updated")
105
113
  end
106
114
  end
107
115
  end
@@ -8,32 +8,18 @@ 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
24
- end
25
-
26
- def project_id=(value)
27
- value = value.to_s unless value.nil?
28
- return if project_id == value
29
-
30
- clear_local(verbose: false)
31
- git['projectId'] = value
32
- end
33
-
34
- def task_id=(value)
35
- value = value.to_s unless value.nil?
36
- git['taskId'] = value
21
+ def path=(new_path)
22
+ git["path"] = new_path
37
23
  end
38
24
 
39
25
  def clear_local(verbose: true)
@@ -41,44 +27,49 @@ module Abt
41
27
  end
42
28
 
43
29
  def clear_global(verbose: true)
44
- git.global.clear(output: verbose ? cli.err_output : nil)
30
+ git_global.clear(output: verbose ? cli.err_output : nil)
45
31
  end
46
32
 
47
33
  def access_token
48
- return git.global['accessToken'] unless git.global['accessToken'].nil?
34
+ return git_global["accessToken"] unless git_global["accessToken"].nil?
49
35
 
50
- git.global['accessToken'] = cli.prompt.text([
51
- 'Please provide your personal access token for Harvest.',
52
- 'If you don\'t have one, create one here: https://id.getharvest.com/developers',
53
- '',
54
- 'Enter access token'
36
+ git_global["accessToken"] = cli.prompt.text([
37
+ "Please provide your personal access token for Harvest.",
38
+ "If you don't have one, create one here: https://id.getharvest.com/developers",
39
+ "",
40
+ "Enter access token"
55
41
  ].join("\n"))
56
42
  end
57
43
 
58
44
  def account_id
59
- return git.global['accountId'] unless git.global['accountId'].nil?
45
+ return git_global["accountId"] unless git_global["accountId"].nil?
60
46
 
61
- git.global['accountId'] = cli.prompt.text([
62
- 'Please provide harvest account id.',
63
- 'This information is shown next to your generated access token',
64
- '',
65
- 'Enter account id'
47
+ git_global["accountId"] = cli.prompt.text([
48
+ "Please provide harvest account id.",
49
+ "This information is shown next to your generated access token",
50
+ "",
51
+ "Enter account id"
66
52
  ].join("\n"))
67
53
  end
68
54
 
69
55
  def user_id
70
- return git.global['userId'] unless git.global['userId'].nil?
56
+ return git_global["userId"] unless git_global["userId"].nil?
71
57
 
72
- git.global['userId'] = api.get('users/me')['id'].to_s
58
+ git_global["userId"] = api.get("users/me")["id"].to_s
73
59
  end
74
60
 
75
61
  private
76
62
 
77
- 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
78
70
 
79
71
  def api
80
- @api ||=
81
- 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)
82
73
  end
83
74
  end
84
75
  end