abt-cli 0.0.21 → 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 (66) 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 +1 -1
  5. data/lib/abt/ari_list.rb +1 -1
  6. data/lib/abt/base_command.rb +7 -7
  7. data/lib/abt/cli.rb +27 -40
  8. data/lib/abt/cli/arguments_parser.rb +5 -9
  9. data/lib/abt/cli/global_commands.rb +23 -0
  10. data/lib/abt/cli/global_commands/commands.rb +2 -2
  11. data/lib/abt/cli/global_commands/examples.rb +2 -2
  12. data/lib/abt/cli/global_commands/help.rb +2 -2
  13. data/lib/abt/cli/global_commands/readme.rb +2 -2
  14. data/lib/abt/cli/global_commands/share.rb +6 -6
  15. data/lib/abt/cli/global_commands/version.rb +2 -2
  16. data/lib/abt/cli/prompt.rb +51 -20
  17. data/lib/abt/docs.rb +39 -33
  18. data/lib/abt/docs/cli.rb +3 -3
  19. data/lib/abt/docs/markdown.rb +5 -5
  20. data/lib/abt/git_config.rb +4 -6
  21. data/lib/abt/providers/asana/api.rb +9 -9
  22. data/lib/abt/providers/asana/base_command.rb +8 -10
  23. data/lib/abt/providers/asana/commands/add.rb +13 -12
  24. data/lib/abt/providers/asana/commands/branch_name.rb +8 -8
  25. data/lib/abt/providers/asana/commands/clear.rb +7 -8
  26. data/lib/abt/providers/asana/commands/current.rb +14 -14
  27. data/lib/abt/providers/asana/commands/finalize.rb +11 -12
  28. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +11 -11
  29. data/lib/abt/providers/asana/commands/init.rb +8 -41
  30. data/lib/abt/providers/asana/commands/pick.rb +17 -17
  31. data/lib/abt/providers/asana/commands/projects.rb +5 -5
  32. data/lib/abt/providers/asana/commands/share.rb +5 -5
  33. data/lib/abt/providers/asana/commands/start.rb +21 -20
  34. data/lib/abt/providers/asana/commands/tasks.rb +6 -6
  35. data/lib/abt/providers/asana/configuration.rb +25 -25
  36. data/lib/abt/providers/asana/path.rb +5 -5
  37. data/lib/abt/providers/devops/api.rb +12 -12
  38. data/lib/abt/providers/devops/base_command.rb +10 -10
  39. data/lib/abt/providers/devops/commands/boards.rb +5 -7
  40. data/lib/abt/providers/devops/commands/branch_name.rb +9 -9
  41. data/lib/abt/providers/devops/commands/clear.rb +7 -8
  42. data/lib/abt/providers/devops/commands/current.rb +17 -17
  43. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +13 -13
  44. data/lib/abt/providers/devops/commands/init.rb +17 -13
  45. data/lib/abt/providers/devops/commands/pick.rb +11 -11
  46. data/lib/abt/providers/devops/commands/share.rb +5 -5
  47. data/lib/abt/providers/devops/commands/{work-items.rb → work_items.rb} +3 -3
  48. data/lib/abt/providers/devops/configuration.rb +19 -15
  49. data/lib/abt/providers/devops/path.rb +5 -4
  50. data/lib/abt/providers/git/commands/branch.rb +17 -19
  51. data/lib/abt/providers/harvest/api.rb +8 -8
  52. data/lib/abt/providers/harvest/base_command.rb +6 -8
  53. data/lib/abt/providers/harvest/commands/clear.rb +7 -8
  54. data/lib/abt/providers/harvest/commands/current.rb +13 -13
  55. data/lib/abt/providers/harvest/commands/init.rb +10 -38
  56. data/lib/abt/providers/harvest/commands/pick.rb +11 -11
  57. data/lib/abt/providers/harvest/commands/projects.rb +5 -5
  58. data/lib/abt/providers/harvest/commands/share.rb +5 -5
  59. data/lib/abt/providers/harvest/commands/start.rb +5 -3
  60. data/lib/abt/providers/harvest/commands/stop.rb +12 -12
  61. data/lib/abt/providers/harvest/commands/tasks.rb +7 -7
  62. data/lib/abt/providers/harvest/commands/track.rb +21 -20
  63. data/lib/abt/providers/harvest/configuration.rb +18 -18
  64. data/lib/abt/providers/harvest/path.rb +5 -5
  65. data/lib/abt/version.rb +1 -1
  66. metadata +6 -5
@@ -15,18 +15,18 @@ module Abt
15
15
  end
16
16
 
17
17
  def path
18
- Path.new(local_available? && git['path'] || '')
18
+ Path.new(local_available? && git["path"] || "")
19
19
  end
20
20
 
21
21
  def path=(new_path)
22
- git['path'] = new_path
22
+ git["path"] = new_path
23
23
  end
24
24
 
25
25
  def workspace_gid
26
26
  @workspace_gid ||= begin
27
- current = git_global['workspaceGid']
27
+ current = git_global["workspaceGid"]
28
28
  if current.nil?
29
- prompt_workspace['gid']
29
+ prompt_workspace["gid"]
30
30
  else
31
31
  current
32
32
  end
@@ -36,13 +36,13 @@ module Abt
36
36
  def wip_section_gid
37
37
  return nil unless local_available?
38
38
 
39
- @wip_section_gid ||= git['wipSectionGid'] || prompt_wip_section['gid']
39
+ @wip_section_gid ||= git["wipSectionGid"] || prompt_wip_section["gid"]
40
40
  end
41
41
 
42
42
  def finalized_section_gid
43
43
  return nil unless local_available?
44
44
 
45
- @finalized_section_gid ||= git['finalizedSectionGid'] || prompt_finalized_section['gid']
45
+ @finalized_section_gid ||= git["finalizedSectionGid"] || prompt_finalized_section["gid"]
46
46
  end
47
47
 
48
48
  def clear_local(verbose: true)
@@ -54,57 +54,57 @@ module Abt
54
54
  end
55
55
 
56
56
  def access_token
57
- return git_global['accessToken'] unless git_global['accessToken'].nil?
57
+ return git_global["accessToken"] unless git_global["accessToken"].nil?
58
58
 
59
- git_global['accessToken'] = cli.prompt.text([
60
- 'Please provide your personal access token for Asana.',
61
- 'If you don\'t have one, create one here: https://app.asana.com/0/developer-console',
62
- '',
63
- 'Enter access token'
59
+ git_global["accessToken"] = cli.prompt.text([
60
+ "Please provide your personal access token for Asana.",
61
+ "If you don't have one, create one here: https://app.asana.com/0/developer-console",
62
+ "",
63
+ "Enter access token"
64
64
  ].join("\n"))
65
65
  end
66
66
 
67
67
  private
68
68
 
69
69
  def git
70
- @git ||= GitConfig.new('local', 'abt.asana')
70
+ @git ||= GitConfig.new("local", "abt.asana")
71
71
  end
72
72
 
73
73
  def git_global
74
- @git_global ||= GitConfig.new('global', 'abt.asana')
74
+ @git_global ||= GitConfig.new("global", "abt.asana")
75
75
  end
76
76
 
77
77
  def prompt_finalized_section
78
78
  section = prompt_section('Select section for finalized tasks (E.g. "Merged")')
79
- git['finalizedSectionGid'] = section['gid']
79
+ git["finalizedSectionGid"] = section["gid"]
80
80
  section
81
81
  end
82
82
 
83
83
  def prompt_wip_section
84
- section = prompt_section('Select WIP (Work In Progress) section')
85
- git['wipSectionGid'] = section['gid']
84
+ section = prompt_section("Select WIP (Work In Progress) section")
85
+ git["wipSectionGid"] = section["gid"]
86
86
  section
87
87
  end
88
88
 
89
89
  def prompt_section(message)
90
- cli.warn 'Fetching sections...'
91
- sections = api.get_paged("projects/#{path.project_gid}/sections", opt_fields: 'name')
90
+ cli.warn("Fetching sections...")
91
+ sections = api.get_paged("projects/#{path.project_gid}/sections", opt_fields: "name")
92
92
  cli.prompt.choice(message, sections)
93
93
  end
94
94
 
95
95
  def prompt_workspace
96
- cli.warn 'Fetching workspaces...'
97
- workspaces = api.get_paged('workspaces', opt_fields: 'name')
96
+ cli.warn("Fetching workspaces...")
97
+ workspaces = api.get_paged("workspaces", opt_fields: "name")
98
98
  if workspaces.empty?
99
- cli.abort 'Your asana access token does not have access to any workspaces'
99
+ cli.abort("Your asana access token does not have access to any workspaces")
100
100
  elsif workspaces.one?
101
101
  workspace = workspaces.first
102
- cli.warn "Selected Asana workspace: #{workspace['name']}"
102
+ cli.warn("Selected Asana workspace: #{workspace['name']}")
103
103
  else
104
- workspace = cli.prompt.choice('Select Asana workspace', workspaces)
104
+ workspace = cli.prompt.choice("Select Asana workspace", workspaces)
105
105
  end
106
106
 
107
- git_global['workspaceGid'] = workspace['gid']
107
+ git_global["workspaceGid"] = workspace["gid"]
108
108
  workspace
109
109
  end
110
110
 
@@ -4,15 +4,15 @@ module Abt
4
4
  module Providers
5
5
  module Asana
6
6
  class Path < String
7
- PATH_REGEX = %r{^(?<project_gid>\d+)?(/(?<task_gid>\d+))?$}.freeze
7
+ PATH_REGEX = %r{^(?<project_gid>\d+)?/?(?<task_gid>\d+)?$}.freeze
8
8
 
9
9
  def self.from_ids(project_gid = nil, task_gid = nil)
10
- path = project_gid ? [project_gid, *task_gid].join('/') : ''
11
- new path
10
+ path = project_gid ? [project_gid, *task_gid].join("/") : ""
11
+ new(path)
12
12
  end
13
13
 
14
- def initialize(path = '')
15
- raise Abt::Cli::Abort, "Invalid path: #{path}" unless path =~ PATH_REGEX
14
+ def initialize(path = "")
15
+ raise Abt::Cli::Abort, "Invalid path: #{path}" unless PATH_REGEX.match?(path)
16
16
 
17
17
  super
18
18
  end
@@ -4,9 +4,9 @@ module Abt
4
4
  module Providers
5
5
  module Devops
6
6
  class Api
7
- VERBS = %i[get post put].freeze
7
+ VERBS = [:get, :post, :put].freeze
8
8
 
9
- CONDITIONAL_ACCESS_POLICY_ERROR_CODE = 'VS403463'
9
+ CONDITIONAL_ACCESS_POLICY_ERROR_CODE = "VS403463"
10
10
 
11
11
  attr_reader :organization_name, :project_name, :username, :access_token, :cli
12
12
 
@@ -26,18 +26,18 @@ module Abt
26
26
 
27
27
  def get_paged(path, query = {})
28
28
  result = request(:get, path, query)
29
- result['value']
29
+ result["value"]
30
30
 
31
31
  # TODO: Loop if necessary
32
32
  end
33
33
 
34
34
  def work_item_query(wiql)
35
- response = post('wit/wiql', Oj.dump({ query: wiql }, mode: :json))
36
- ids = response['workItems'].map { |work_item| work_item['id'] }
35
+ response = post("wit/wiql", Oj.dump({ query: wiql }, mode: :json))
36
+ ids = response["workItems"].map { |work_item| work_item["id"] }
37
37
 
38
38
  work_items = []
39
39
  ids.each_slice(200) do |page_ids|
40
- work_items += get_paged('wit/workitems', ids: page_ids.join(','))
40
+ work_items += get_paged("wit/workitems", ids: page_ids.join(","))
41
41
  end
42
42
 
43
43
  work_items
@@ -50,7 +50,7 @@ module Abt
50
50
  Oj.load(response.body)
51
51
  else
52
52
  error_class = Abt::HttpError.error_class_for_status(response.status)
53
- encoded_response_body = response.body.force_encoding('utf-8')
53
+ encoded_response_body = response.body.force_encoding("utf-8")
54
54
  raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
55
55
  end
56
56
  rescue Abt::HttpError::ForbiddenError => e
@@ -75,9 +75,9 @@ module Abt
75
75
 
76
76
  def connection
77
77
  @connection ||= Faraday.new(api_endpoint) do |connection|
78
- connection.basic_auth username, access_token
79
- connection.headers['Content-Type'] = 'application/json'
80
- connection.headers['Accept'] = 'application/json; api-version=6.0'
78
+ connection.basic_auth(username, access_token)
79
+ connection.headers["Content-Type"] = "application/json"
80
+ connection.headers["Accept"] = "application/json; api-version=6.0"
81
81
  end
82
82
  end
83
83
 
@@ -87,14 +87,14 @@ module Abt
87
87
  # https://apidock.com/ruby/ERB/Util/url_encode
88
88
  def rfc_3986_encode_path_segment(string)
89
89
  string.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/) do |match|
90
- format('%%%02X', match.unpack1('C')) # rubocop:disable Style/FormatStringToken
90
+ format("%%%02X", match.unpack1("C"))
91
91
  end
92
92
  end
93
93
 
94
94
  def handle_denied_by_conditional_access_policy!(exception)
95
95
  raise exception unless exception.message.include?(CONDITIONAL_ACCESS_POLICY_ERROR_CODE)
96
96
 
97
- cli.abort <<~TXT
97
+ cli.abort(<<~TXT)
98
98
  Access denied by conditional access policy.
99
99
  Try logging into the board using the URL below, then retry the command.
100
100
 
@@ -22,41 +22,41 @@ module Abt
22
22
  def require_board!
23
23
  return if organization_name && project_name && board_id
24
24
 
25
- abort 'No current/specified board. Did you initialize DevOps?'
25
+ abort("No current/specified board. Did you initialize DevOps?")
26
26
  end
27
27
 
28
28
  def require_work_item!
29
29
  unless organization_name && project_name && board_id
30
- abort 'No current/specified board. Did you initialize DevOps and pick a work item?'
30
+ abort("No current/specified board. Did you initialize DevOps and pick a work item?")
31
31
  end
32
32
 
33
33
  return if work_item_id
34
34
 
35
- abort 'No current/specified work item. Did you pick a DevOps work item?'
35
+ abort("No current/specified work item. Did you pick a DevOps work item?")
36
36
  end
37
37
 
38
38
  def sanitize_work_item(work_item)
39
39
  return nil if work_item.nil?
40
40
 
41
41
  work_item.merge(
42
- 'id' => work_item['id'].to_s,
43
- 'name' => work_item['fields']['System.Title'],
44
- 'url' => api.url_for_work_item(work_item)
42
+ "id" => work_item["id"].to_s,
43
+ "name" => work_item["fields"]["System.Title"],
44
+ "url" => api.url_for_work_item(work_item)
45
45
  )
46
46
  end
47
47
 
48
48
  def print_board(organization_name, project_name, board)
49
49
  path = "#{organization_name}/#{project_name}/#{board['id']}"
50
50
 
51
- cli.print_ari('devops', path, board['name'])
52
- warn api.url_for_board(board) if cli.output.isatty
51
+ cli.print_ari("devops", path, board["name"])
52
+ warn(api.url_for_board(board)) if cli.output.isatty
53
53
  end
54
54
 
55
55
  def print_work_item(organization, project, board, work_item)
56
56
  path = "#{organization}/#{project}/#{board['id']}/#{work_item['id']}"
57
57
 
58
- cli.print_ari('devops', path, work_item['name'])
59
- warn work_item['url'] if work_item.key?('url') && cli.output.isatty
58
+ cli.print_ari("devops", path, work_item["name"])
59
+ warn(work_item["url"]) if work_item.key?("url") && cli.output.isatty
60
60
  end
61
61
 
62
62
  def api
@@ -6,18 +6,16 @@ module Abt
6
6
  module Commands
7
7
  class Boards < BaseCommand
8
8
  def self.usage
9
- 'abt boards devops'
9
+ "abt boards devops"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'List all boards - useful for piping into grep etc'
13
+ "List all boards - useful for piping into grep etc"
14
14
  end
15
15
 
16
16
  def perform
17
- if organization_name.nil?
18
- abort 'No organization selected. Did you initialize DevOps?'
19
- end
20
- abort 'No project selected. Did you initialize DevOps?' if project_name.nil?
17
+ abort("No organization selected. Did you initialize DevOps?") if organization_name.nil?
18
+ abort("No project selected. Did you initialize DevOps?") if project_name.nil?
21
19
 
22
20
  boards.map do |board|
23
21
  print_board(organization_name, project_name, board)
@@ -27,7 +25,7 @@ module Abt
27
25
  private
28
26
 
29
27
  def boards
30
- @boards ||= api.get_paged('work/boards')
28
+ @boards ||= api.get_paged("work/boards")
31
29
  end
32
30
  end
33
31
  end
@@ -6,11 +6,11 @@ module Abt
6
6
  module Commands
7
7
  class BranchName < BaseCommand
8
8
  def self.usage
9
- 'abt branch-name devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
9
+ "abt branch-name devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Suggest a git branch name for the current/specified work-item.'
13
+ "Suggest a git branch name for the current/specified work-item."
14
14
  end
15
15
 
16
16
  def perform
@@ -21,24 +21,24 @@ module Abt
21
21
  args = [organization_name, project_name, board_id, work_item_id].compact
22
22
 
23
23
  error_message = [
24
- 'Unable to find work item for configuration:',
24
+ "Unable to find work item for configuration:",
25
25
  "devops:#{args.join('/')}"
26
26
  ].join("\n")
27
- abort error_message
27
+ abort(error_message)
28
28
  end
29
29
 
30
30
  private
31
31
 
32
32
  def name
33
- str = work_item['id']
34
- str += '-'
35
- str += work_item['name'].downcase.gsub(/[^\w]/, '-')
36
- str.gsub(/-+/, '-')
33
+ str = work_item["id"]
34
+ str += "-"
35
+ str += work_item["name"].downcase.gsub(/[^\w]/, "-")
36
+ str.squeeze("-").gsub(/(^-|-$)/, "")
37
37
  end
38
38
 
39
39
  def work_item
40
40
  @work_item ||= begin
41
- work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
41
+ work_item = api.get_paged("wit/workitems", ids: work_item_id)[0]
42
42
  sanitize_work_item(work_item)
43
43
  end
44
44
  end
@@ -6,29 +6,28 @@ module Abt
6
6
  module Commands
7
7
  class Clear < BaseCommand
8
8
  def self.usage
9
- 'abt clear devops'
9
+ "abt clear devops"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Clear DevOps configuration'
13
+ "Clear DevOps configuration"
14
14
  end
15
15
 
16
16
  def self.flags
17
17
  [
18
- ['-g', '--global', 'Clear global instead of local DevOp configuration (credentials etc.)'],
19
- ['-a', '--all', 'Clear all DevOp configuration']
18
+ ["-g", "--global",
19
+ "Clear global instead of local DevOp configuration (credentials etc.)"],
20
+ ["-a", "--all", "Clear all DevOp configuration"]
20
21
  ]
21
22
  end
22
23
 
23
24
  def perform
24
- if flags[:global] && flags[:all]
25
- abort('Flags --global and --all cannot be used at the same time')
26
- end
25
+ abort("Flags --global and --all cannot be used at the same time") if flags[:global] && flags[:all]
27
26
 
28
27
  config.clear_local unless flags[:global]
29
28
  config.clear_global if flags[:global] || flags[:all]
30
29
 
31
- warn 'Configuration cleared'
30
+ warn("Configuration cleared")
32
31
  end
33
32
  end
34
33
  end
@@ -6,22 +6,22 @@ module Abt
6
6
  module Commands
7
7
  class Current < BaseCommand
8
8
  def self.usage
9
- 'abt current devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
9
+ "abt current devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]"
10
10
  end
11
11
 
12
12
  def self.description
13
- 'Get or set DevOps configuration for current git repository'
13
+ "Get or set DevOps configuration for current git repository"
14
14
  end
15
15
 
16
16
  def perform
17
- 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
  require_board!
20
20
  ensure_valid_configuration!
21
21
 
22
22
  if path != config.path && config.local_available?
23
23
  config.path = path
24
- warn 'Configuration updated'
24
+ warn("Configuration updated")
25
25
  end
26
26
 
27
27
  print_configuration
@@ -39,28 +39,28 @@ module Abt
39
39
 
40
40
  def ensure_valid_configuration!
41
41
  if board.nil?
42
- abort 'Board could not be found, ensure that settings for organization, project, and board are correct'
42
+ abort("Board could not be found, ensure that settings for organization, project, and board are correct")
43
43
  end
44
- abort "No such work item: ##{work_item_id}" if work_item_id && work_item.nil?
44
+ abort("No such work item: ##{work_item_id}") if work_item_id && work_item.nil?
45
45
  end
46
46
 
47
47
  def board
48
48
  @board ||= begin
49
- warn 'Fetching board...'
50
- api.get("work/boards/#{board_id}")
51
- rescue HttpError::NotFoundError
52
- nil
53
- end
49
+ warn("Fetching board...")
50
+ api.get("work/boards/#{board_id}")
51
+ rescue HttpError::NotFoundError
52
+ nil
53
+ end
54
54
  end
55
55
 
56
56
  def work_item
57
57
  @work_item ||= begin
58
- warn 'Fetching work item...'
59
- work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
60
- sanitize_work_item(work_item)
61
- rescue HttpError::NotFoundError
62
- nil
63
- end
58
+ warn("Fetching work item...")
59
+ work_item = api.get_paged("wit/workitems", ids: work_item_id)[0]
60
+ sanitize_work_item(work_item)
61
+ rescue HttpError::NotFoundError
62
+ nil
63
+ end
64
64
  end
65
65
  end
66
66
  end