abt-cli 0.0.16 → 0.0.21

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/lib/abt/ari.rb +20 -0
  3. data/lib/abt/ari_list.rb +13 -0
  4. data/lib/abt/base_command.rb +63 -0
  5. data/lib/abt/cli.rb +59 -47
  6. data/lib/abt/cli/arguments_parser.rb +9 -24
  7. data/lib/abt/cli/global_commands/commands.rb +23 -0
  8. data/lib/abt/cli/global_commands/examples.rb +23 -0
  9. data/lib/abt/cli/global_commands/help.rb +23 -0
  10. data/lib/abt/cli/global_commands/readme.rb +23 -0
  11. data/lib/abt/cli/global_commands/share.rb +36 -0
  12. data/lib/abt/cli/global_commands/version.rb +23 -0
  13. data/lib/abt/cli/prompt.rb +5 -4
  14. data/lib/abt/docs.rb +32 -15
  15. data/lib/abt/docs/cli.rb +5 -5
  16. data/lib/abt/docs/markdown.rb +8 -7
  17. data/lib/abt/git_config.rb +20 -36
  18. data/lib/abt/providers/asana/base_command.rb +15 -35
  19. data/lib/abt/providers/asana/commands/add.rb +9 -7
  20. data/lib/abt/providers/asana/commands/branch_name.rb +9 -4
  21. data/lib/abt/providers/asana/commands/clear.rb +2 -0
  22. data/lib/abt/providers/asana/commands/current.rb +19 -34
  23. data/lib/abt/providers/asana/commands/finalize.rb +5 -9
  24. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +9 -4
  25. data/lib/abt/providers/asana/commands/init.rb +6 -6
  26. data/lib/abt/providers/asana/commands/pick.rb +16 -11
  27. data/lib/abt/providers/asana/commands/projects.rb +1 -1
  28. data/lib/abt/providers/asana/commands/share.rb +5 -7
  29. data/lib/abt/providers/asana/commands/start.rb +14 -12
  30. data/lib/abt/providers/asana/commands/tasks.rb +4 -3
  31. data/lib/abt/providers/asana/configuration.rb +20 -26
  32. data/lib/abt/providers/asana/path.rb +36 -0
  33. data/lib/abt/providers/devops/api.rb +12 -0
  34. data/lib/abt/providers/devops/base_command.rb +15 -40
  35. data/lib/abt/providers/devops/commands/boards.rb +2 -2
  36. data/lib/abt/providers/devops/commands/branch_name.rb +7 -3
  37. data/lib/abt/providers/devops/commands/clear.rb +2 -0
  38. data/lib/abt/providers/devops/commands/current.rb +14 -38
  39. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +9 -1
  40. data/lib/abt/providers/devops/commands/init.rb +15 -15
  41. data/lib/abt/providers/devops/commands/pick.rb +5 -12
  42. data/lib/abt/providers/devops/commands/share.rb +6 -5
  43. data/lib/abt/providers/devops/commands/work-items.rb +1 -1
  44. data/lib/abt/providers/devops/configuration.rb +17 -46
  45. data/lib/abt/providers/devops/path.rb +50 -0
  46. data/lib/abt/providers/git/commands/branch.rb +22 -16
  47. data/lib/abt/providers/harvest/base_command.rb +16 -34
  48. data/lib/abt/providers/harvest/commands/clear.rb +2 -0
  49. data/lib/abt/providers/harvest/commands/current.rb +24 -31
  50. data/lib/abt/providers/harvest/commands/init.rb +5 -6
  51. data/lib/abt/providers/harvest/commands/pick.rb +3 -4
  52. data/lib/abt/providers/harvest/commands/projects.rb +1 -1
  53. data/lib/abt/providers/harvest/commands/share.rb +5 -7
  54. data/lib/abt/providers/harvest/commands/start.rb +1 -1
  55. data/lib/abt/providers/harvest/commands/stop.rb +7 -7
  56. data/lib/abt/providers/harvest/commands/tasks.rb +4 -1
  57. data/lib/abt/providers/harvest/commands/track.rb +26 -19
  58. data/lib/abt/providers/harvest/configuration.rb +20 -29
  59. data/lib/abt/providers/harvest/path.rb +36 -0
  60. data/lib/abt/version.rb +1 -1
  61. metadata +14 -3
  62. data/lib/abt/cli/base_command.rb +0 -61
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ class Path < String
7
+ ORGANIZATION_NAME_REGEX = %r{(?<organization_name>[^/ ]+)}.freeze
8
+ PROJECT_NAME_REGEX = %r{(?<project_name>[^/ ]+)}.freeze
9
+ BOARD_ID_REGEX = /(?<board_id>[a-z0-9\-]+)/.freeze
10
+ WORK_ITEM_ID_REGEX = /(?<work_item_id>\d+)/.freeze
11
+
12
+ PATH_REGEX = %r{^(#{ORGANIZATION_NAME_REGEX}/#{PROJECT_NAME_REGEX}/#{BOARD_ID_REGEX})?(/#{WORK_ITEM_ID_REGEX})?}.freeze
13
+
14
+ def self.from_ids(organization_id = nil, project_name = nil, board_id = nil, work_item_id = nil)
15
+ return new unless organization_id && project_name && board_id
16
+
17
+ new [organization_id, project_name, board_id, *work_item_id].join('/')
18
+ end
19
+
20
+ def initialize(path = '')
21
+ raise Abt::Cli::Abort, "Invalid path: #{path}" unless path =~ PATH_REGEX
22
+
23
+ super
24
+ end
25
+
26
+ def organization_name
27
+ match[:organization_name]
28
+ end
29
+
30
+ def project_name
31
+ match[:project_name]
32
+ end
33
+
34
+ def board_id
35
+ match[:board_id]
36
+ end
37
+
38
+ def work_item_id
39
+ match[:work_item_id]
40
+ end
41
+
42
+ private
43
+
44
+ def match
45
+ @match ||= PATH_REGEX.match(self)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -4,7 +4,7 @@ module Abt
4
4
  module Providers
5
5
  module Git
6
6
  module Commands
7
- class Branch < Abt::Cli::BaseCommand
7
+ class Branch < Abt::BaseCommand
8
8
  def self.usage
9
9
  'abt branch git <scheme>[:<path>]'
10
10
  end
@@ -14,23 +14,23 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- create_and_switch unless switch
18
- cli.warn "Switched to #{branch_name}"
17
+ switch || create_and_switch
18
+ warn "Switched to #{branch_name}"
19
19
  end
20
20
 
21
21
  private
22
22
 
23
23
  def switch
24
24
  success = false
25
- Open3.popen3("git switch #{branch_name}") do |_i, _o, _error_output, thread|
25
+ Open3.popen3("git switch #{branch_name}") do |_i, _o, _e, thread|
26
26
  success = thread.value.success?
27
27
  end
28
28
  success
29
29
  end
30
30
 
31
31
  def create_and_switch
32
- cli.warn "No such branch: #{branch_name}"
33
- cli.abort('Aborting') unless cli.prompt.boolean 'Create branch?'
32
+ warn "No such branch: #{branch_name}"
33
+ abort('Aborting') unless cli.prompt.boolean 'Create branch?'
34
34
 
35
35
  Open3.popen3("git switch -c #{branch_name}") do |_i, _o, _e, thread|
36
36
  thread.value
@@ -39,29 +39,35 @@ module Abt
39
39
 
40
40
  def branch_name # rubocop:disable Metrics/MethodLength
41
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`.',
42
+ if branch_names_from_aris.empty?
43
+ abort [
44
+ 'None of the specified ARIs responded to `branch-name`.',
45
45
  'Did you add compatible scheme? e.g.:',
46
46
  ' abt branch git asana',
47
47
  ' abt branch git devops'
48
48
  ].join("\n")
49
49
  end
50
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',
51
+ if branch_names_from_aris.length > 1
52
+ abort [
53
+ 'Got branch names from multiple ARIs, only one is supported',
54
54
  'Branch names were:',
55
- *branch_names_from_scheme_arguments.map { |name| " #{name}" }
55
+ *branch_names_from_aris.map { |name| " #{name}" }
56
56
  ].join("\n")
57
57
  end
58
58
 
59
- branch_names_from_scheme_arguments.first
59
+ branch_names_from_aris.first
60
60
  end
61
61
  end
62
62
 
63
- def branch_names_from_scheme_arguments
64
- input = StringIO.new(cli.scheme_arguments.to_s)
63
+ def branch_names_from_aris
64
+ other_aris = cli.aris - [ari]
65
+
66
+ if other_aris.empty?
67
+ abort 'You must provide an additional ARI that responds to: branch-name. E.g., asana'
68
+ end
69
+
70
+ input = StringIO.new(cli.aris.to_s)
65
71
  output = StringIO.new
66
72
  Abt::Cli.new(argv: ['branch-name'], output: output, input: input).perform
67
73
 
@@ -3,40 +3,38 @@
3
3
  module Abt
4
4
  module Providers
5
5
  module Harvest
6
- class BaseCommand < Abt::Cli::BaseCommand
7
- attr_reader :path, :flags, :project_id, :task_id, :cli, :config
6
+ class BaseCommand < Abt::BaseCommand
7
+ extend Forwardable
8
8
 
9
- def initialize(path:, cli:, **)
9
+ attr_reader :config, :path
10
+
11
+ def_delegators(:@path, :project_id, :task_id)
12
+
13
+ def initialize(ari:, cli:)
10
14
  super
11
15
 
12
16
  @config = Configuration.new(cli: cli)
13
-
14
- if path.nil?
15
- use_current_path
16
- else
17
- use_path(path)
18
- end
17
+ @path = ari.path ? Path.new(ari.path) : config.path
19
18
  end
20
19
 
21
20
  private
22
21
 
23
22
  def require_project!
24
- cli.abort 'No current/specified project. Did you initialize Harvest?' if project_id.nil?
23
+ return if project_id
24
+
25
+ abort 'No current/specified project. Did you initialize Harvest?'
25
26
  end
26
27
 
27
28
  def require_task!
28
- if project_id.nil?
29
- cli.abort 'No current/specified project. Did you initialize Harvest and pick a task?'
29
+ unless project_id
30
+ abort 'No current/specified project. Did you initialize Harvest and pick a task?'
30
31
  end
31
- cli.abort 'No current/specified task. Did you pick a Harvest task?' if task_id.nil?
32
- end
33
32
 
34
- def same_args_as_config?
35
- project_id == config.project_id && task_id == config.task_id
33
+ abort 'No current/specified task. Did you pick a Harvest task?' if task_id.nil?
36
34
  end
37
35
 
38
36
  def print_project(project)
39
- cli.print_scheme_argument(
37
+ cli.print_ari(
40
38
  'harvest',
41
39
  project['id'],
42
40
  "#{project['client']['name']} > #{project['name']}"
@@ -44,29 +42,13 @@ module Abt
44
42
  end
45
43
 
46
44
  def print_task(project, task)
47
- cli.print_scheme_argument(
45
+ cli.print_ari(
48
46
  'harvest',
49
47
  "#{project['id']}/#{task['id']}",
50
48
  "#{project['name']} > #{task['name']}"
51
49
  )
52
50
  end
53
51
 
54
- def use_current_path
55
- @project_id = config.project_id
56
- @task_id = config.task_id
57
- end
58
-
59
- def use_path(path)
60
- args = path.to_s.split('/')
61
- @project_id = args[0].to_s
62
- @project_id = nil if project_id.empty?
63
-
64
- return if project_id.nil?
65
-
66
- @task_id = args[1].to_s
67
- @task_id = nil if @task_id.empty?
68
- end
69
-
70
52
  def api
71
53
  @api ||= Abt::Providers::Harvest::Api.new(access_token: config.access_token,
72
54
  account_id: config.account_id)
@@ -27,6 +27,8 @@ module Abt
27
27
 
28
28
  config.clear_local unless flags[:global]
29
29
  config.clear_global if flags[:global] || flags[:all]
30
+
31
+ warn 'Configuration cleared'
30
32
  end
31
33
  end
32
34
  end
@@ -14,57 +14,50 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
+ abort 'Must be run inside a git repository' unless config.local_available?
18
+
17
19
  require_project!
20
+ ensure_valid_configuration!
18
21
 
19
- if same_args_as_config? || !config.local_available?
20
- show_current_configuration
21
- else
22
- cli.warn 'Updating configuration'
23
- update_configuration
22
+ if path != config.path
23
+ config.path = path
24
+ warn 'Configuration updated'
24
25
  end
25
- end
26
-
27
- private
28
26
 
29
- def show_current_configuration
30
- if task_id.nil?
31
- print_project(project)
32
- else
33
- print_task(project, task)
34
- end
27
+ print_configuration
35
28
  end
36
29
 
37
- def update_configuration
38
- ensure_project_is_valid!
39
- config.project_id = project_id
30
+ private
40
31
 
32
+ def print_configuration
41
33
  if task_id.nil?
42
34
  print_project(project)
43
- config.task_id = nil
44
35
  else
45
- ensure_task_is_valid!
46
- config.task_id = task_id
47
-
48
36
  print_task(project, task)
49
37
  end
50
38
  end
51
39
 
52
- def ensure_project_is_valid!
53
- cli.abort "Invalid project: #{project_id}" if project.nil?
54
- end
55
-
56
- def ensure_task_is_valid!
57
- cli.abort "Invalid task: #{task_id}" if task.nil?
40
+ def ensure_valid_configuration!
41
+ abort "Invalid project: #{project_id}" if project.nil?
42
+ abort "Invalid task: #{task_id}" if task_id && task.nil?
58
43
  end
59
44
 
60
45
  def project
61
- @project ||= project_assignment['project'].merge('client' => project_assignment['client'])
46
+ return @project if instance_variable_defined? :@project
47
+
48
+ @project = if project_assignment
49
+ project_assignment['project'].merge('client' => project_assignment['client'])
50
+ end
62
51
  end
63
52
 
64
53
  def task
65
- @task ||= project_assignment['task_assignments'].map { |ta| ta['task'] }.find do |task|
66
- task['id'].to_s == task_id
67
- end
54
+ return @task if instance_variable_defined? :@task
55
+
56
+ @task = if project_assignment
57
+ project_assignment['task_assignments'].map { |ta| ta['task'] }.find do |task|
58
+ task['id'].to_s == task_id
59
+ end
60
+ end
68
61
  end
69
62
 
70
63
  def project_assignment
@@ -14,13 +14,12 @@ module Abt
14
14
  end
15
15
 
16
16
  def perform
17
- cli.abort 'Must be run inside a git repository' unless config.local_available?
17
+ 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
20
20
  project = find_search_result
21
21
 
22
- config.project_id = project['id']
23
- config.task_id = nil
22
+ config.path = Path.from_ids(project['id'])
24
23
 
25
24
  print_project(project)
26
25
  end
@@ -28,7 +27,7 @@ module Abt
28
27
  private
29
28
 
30
29
  def find_search_result
31
- cli.warn 'Select a project'
30
+ warn 'Select a project'
32
31
 
33
32
  loop do
34
33
  matches = matches_for_string cli.prompt.text('Enter search')
@@ -37,7 +36,7 @@ module Abt
37
36
  next
38
37
  end
39
38
 
40
- cli.warn 'Showing the 10 first matches' if matches.size > 10
39
+ warn 'Showing the 10 first matches' if matches.size > 10
41
40
  choice = cli.prompt.choice 'Select a project', matches[0...10], true
42
41
  break choice['project'] unless choice.nil?
43
42
  end
@@ -66,7 +65,7 @@ module Abt
66
65
 
67
66
  def projects
68
67
  @projects ||= begin
69
- cli.warn 'Fetching projects...'
68
+ warn 'Fetching projects...'
70
69
  project_assignments.map do |project_assignment|
71
70
  project_assignment['project'].merge('client' => project_assignment['client'])
72
71
  end
@@ -20,18 +20,17 @@ module Abt
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']
26
+ warn project['name']
27
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
@@ -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
@@ -10,16 +10,14 @@ module Abt
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
@@ -12,7 +12,7 @@ module Abt
12
12
  end
13
13
 
14
14
  def self.description
15
- 'Alias for: `abt track harvest`. Meant to used in combination other scheme arguments, e.g. `abt start harvest asana`'
15
+ 'Alias for: `abt track harvest`. Meant to used in combination with other ARIs, e.g. `abt start harvest asana`'
16
16
  end
17
17
  end
18
18
  end
@@ -14,21 +14,21 @@ module Abt
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
@@ -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