abt-cli 0.0.21 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
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