abt-cli 0.0.19 → 0.0.24
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.
- checksums.yaml +4 -4
- data/bin/abt +3 -3
- data/lib/abt.rb +6 -6
- data/lib/abt/ari.rb +2 -2
- data/lib/abt/ari_list.rb +1 -1
- data/lib/abt/base_command.rb +7 -7
- data/lib/abt/cli.rb +49 -47
- data/lib/abt/cli/arguments_parser.rb +6 -3
- data/lib/abt/cli/global_commands.rb +23 -0
- data/lib/abt/cli/global_commands/commands.rb +23 -0
- data/lib/abt/cli/global_commands/examples.rb +23 -0
- data/lib/abt/cli/global_commands/help.rb +23 -0
- data/lib/abt/cli/global_commands/readme.rb +23 -0
- data/lib/abt/cli/global_commands/share.rb +36 -0
- data/lib/abt/cli/global_commands/version.rb +23 -0
- data/lib/abt/cli/prompt.rb +64 -52
- data/lib/abt/docs.rb +48 -26
- data/lib/abt/docs/cli.rb +3 -3
- data/lib/abt/docs/markdown.rb +10 -7
- data/lib/abt/git_config.rb +4 -6
- data/lib/abt/helpers.rb +26 -8
- data/lib/abt/providers/asana/api.rb +9 -9
- data/lib/abt/providers/asana/base_command.rb +12 -10
- data/lib/abt/providers/asana/commands/add.rb +13 -12
- data/lib/abt/providers/asana/commands/branch_name.rb +8 -8
- data/lib/abt/providers/asana/commands/clear.rb +7 -8
- data/lib/abt/providers/asana/commands/current.rb +14 -15
- data/lib/abt/providers/asana/commands/finalize.rb +17 -18
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +18 -16
- data/lib/abt/providers/asana/commands/init.rb +8 -41
- data/lib/abt/providers/asana/commands/pick.rb +22 -26
- data/lib/abt/providers/asana/commands/projects.rb +5 -5
- data/lib/abt/providers/asana/commands/share.rb +7 -5
- data/lib/abt/providers/asana/commands/start.rb +28 -21
- data/lib/abt/providers/asana/commands/tasks.rb +6 -6
- data/lib/abt/providers/asana/configuration.rb +37 -29
- data/lib/abt/providers/asana/path.rb +6 -6
- data/lib/abt/providers/devops/api.rb +12 -12
- data/lib/abt/providers/devops/base_command.rb +14 -10
- data/lib/abt/providers/devops/commands/boards.rb +5 -7
- data/lib/abt/providers/devops/commands/branch_name.rb +9 -9
- data/lib/abt/providers/devops/commands/clear.rb +7 -8
- data/lib/abt/providers/devops/commands/current.rb +17 -18
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +21 -19
- data/lib/abt/providers/devops/commands/init.rb +21 -14
- data/lib/abt/providers/devops/commands/pick.rb +25 -19
- data/lib/abt/providers/devops/commands/share.rb +7 -5
- data/lib/abt/providers/devops/commands/{work-items.rb → work_items.rb} +3 -3
- data/lib/abt/providers/devops/configuration.rb +15 -15
- data/lib/abt/providers/devops/path.rb +7 -6
- data/lib/abt/providers/git/commands/branch.rb +23 -21
- data/lib/abt/providers/harvest/api.rb +8 -8
- data/lib/abt/providers/harvest/base_command.rb +10 -8
- data/lib/abt/providers/harvest/commands/clear.rb +7 -8
- data/lib/abt/providers/harvest/commands/current.rb +13 -14
- data/lib/abt/providers/harvest/commands/init.rb +10 -39
- data/lib/abt/providers/harvest/commands/pick.rb +15 -11
- data/lib/abt/providers/harvest/commands/projects.rb +5 -5
- data/lib/abt/providers/harvest/commands/share.rb +7 -5
- data/lib/abt/providers/harvest/commands/start.rb +5 -3
- data/lib/abt/providers/harvest/commands/stop.rb +12 -12
- data/lib/abt/providers/harvest/commands/tasks.rb +7 -7
- data/lib/abt/providers/harvest/commands/track.rb +52 -37
- data/lib/abt/providers/harvest/configuration.rb +18 -18
- data/lib/abt/providers/harvest/path.rb +6 -6
- data/lib/abt/version.rb +1 -1
- metadata +12 -5
@@ -4,8 +4,8 @@ module Abt
|
|
4
4
|
module Providers
|
5
5
|
module Asana
|
6
6
|
class Api
|
7
|
-
API_ENDPOINT =
|
8
|
-
VERBS =
|
7
|
+
API_ENDPOINT = "https://app.asana.com/api/1.0"
|
8
|
+
VERBS = [:get, :post, :put].freeze
|
9
9
|
|
10
10
|
attr_reader :access_token
|
11
11
|
|
@@ -15,7 +15,7 @@ module Abt
|
|
15
15
|
|
16
16
|
VERBS.each do |verb|
|
17
17
|
define_method(verb) do |*args|
|
18
|
-
request(verb, *args)[
|
18
|
+
request(verb, *args)["data"]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -24,10 +24,10 @@ module Abt
|
|
24
24
|
|
25
25
|
loop do
|
26
26
|
result = request(:get, path, query.merge(limit: 100))
|
27
|
-
records += result[
|
28
|
-
break if result[
|
27
|
+
records += result["data"]
|
28
|
+
break if result["next_page"].nil?
|
29
29
|
|
30
|
-
path = result[
|
30
|
+
path = result["next_page"]["path"][1..-1]
|
31
31
|
end
|
32
32
|
|
33
33
|
records
|
@@ -40,15 +40,15 @@ module Abt
|
|
40
40
|
Oj.load(response.body)
|
41
41
|
else
|
42
42
|
error_class = Abt::HttpError.error_class_for_status(response.status)
|
43
|
-
encoded_response_body = response.body.force_encoding(
|
43
|
+
encoded_response_body = response.body.force_encoding("utf-8")
|
44
44
|
raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
def connection
|
49
49
|
@connection ||= Faraday.new(API_ENDPOINT) do |connection|
|
50
|
-
connection.headers[
|
51
|
-
connection.headers[
|
50
|
+
connection.headers["Authorization"] = "Bearer #{access_token}"
|
51
|
+
connection.headers["Content-Type"] = "application/json"
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -20,26 +20,28 @@ module Abt
|
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
+
def require_local_config!
|
24
|
+
abort("Must be run inside a git repository") unless config.local_available?
|
25
|
+
end
|
26
|
+
|
23
27
|
def require_project!
|
24
|
-
abort
|
28
|
+
abort("No current/specified project. Did you initialize Asana?") if project_gid.nil?
|
25
29
|
end
|
26
30
|
|
27
31
|
def require_task!
|
28
|
-
if project_gid.nil?
|
29
|
-
|
30
|
-
end
|
31
|
-
abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
|
32
|
+
abort("No current/specified project. Did you initialize Asana and pick a task?") if project_gid.nil?
|
33
|
+
abort("No current/specified task. Did you pick an Asana task?") if task_gid.nil?
|
32
34
|
end
|
33
35
|
|
34
36
|
def print_project(project)
|
35
|
-
cli.print_ari(
|
36
|
-
warn
|
37
|
+
cli.print_ari("asana", project["gid"], project["name"])
|
38
|
+
warn(project["permalink_url"]) if project.key?("permalink_url") && cli.output.isatty
|
37
39
|
end
|
38
40
|
|
39
41
|
def print_task(project, task)
|
40
|
-
project = {
|
41
|
-
cli.print_ari(
|
42
|
-
warn
|
42
|
+
project = { "gid" => project } if project.is_a?(String)
|
43
|
+
cli.print_ari("asana", "#{project['gid']}/#{task['gid']}", task["name"])
|
44
|
+
warn(task["permalink_url"]) if task.key?("permalink_url") && cli.output.isatty
|
43
45
|
end
|
44
46
|
|
45
47
|
def api
|
@@ -6,22 +6,22 @@ module Abt
|
|
6
6
|
module Commands
|
7
7
|
class Add < BaseCommand
|
8
8
|
def self.usage
|
9
|
-
|
9
|
+
"abt add asana[:<project-gid>]"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Create a new task for the current/specified Asana project"
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
17
|
require_project!
|
18
18
|
|
19
19
|
task
|
20
|
-
warn
|
20
|
+
warn("Task created")
|
21
21
|
|
22
22
|
if section
|
23
23
|
move_task
|
24
|
-
warn
|
24
|
+
warn("Moved to section: #{section['name']}")
|
25
25
|
end
|
26
26
|
|
27
27
|
print_task(project, task)
|
@@ -38,36 +38,37 @@ module Abt
|
|
38
38
|
projects: [project_gid]
|
39
39
|
}
|
40
40
|
}
|
41
|
-
api.post(
|
41
|
+
api.post("tasks", Oj.dump(body, mode: :json))
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
def move_task
|
46
|
-
body = { data: { task: task[
|
46
|
+
body = { data: { task: task["gid"] } }
|
47
47
|
body_json = Oj.dump(body, mode: :json)
|
48
48
|
api.post("sections/#{section['gid']}/addTask", body_json)
|
49
49
|
end
|
50
50
|
|
51
51
|
def name
|
52
|
-
@name ||= cli.prompt.text
|
52
|
+
@name ||= cli.prompt.text("Enter task description")
|
53
53
|
end
|
54
54
|
|
55
55
|
def notes
|
56
|
-
@notes ||= cli.prompt.text
|
56
|
+
@notes ||= cli.prompt.text("Enter task notes")
|
57
57
|
end
|
58
58
|
|
59
59
|
def project
|
60
|
-
@project ||= api.get("projects/#{project_gid}", opt_fields:
|
60
|
+
@project ||= api.get("projects/#{project_gid}", opt_fields: "name")
|
61
61
|
end
|
62
62
|
|
63
63
|
def section
|
64
|
-
@section ||= cli.prompt.choice
|
64
|
+
@section ||= cli.prompt.choice("Add to section?", sections,
|
65
|
+
nil_option: ["q", "Don't add to section"])
|
65
66
|
end
|
66
67
|
|
67
68
|
def sections
|
68
69
|
@sections ||= begin
|
69
|
-
warn
|
70
|
-
api.get_paged("projects/#{project_gid}/sections", opt_fields:
|
70
|
+
warn("Fetching sections...")
|
71
|
+
api.get_paged("projects/#{project_gid}/sections", opt_fields: "name")
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
@@ -6,11 +6,11 @@ module Abt
|
|
6
6
|
module Commands
|
7
7
|
class BranchName < BaseCommand
|
8
8
|
def self.usage
|
9
|
-
|
9
|
+
"abt branch-name asana[:<project-gid>/<task-gid>]"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Suggest a git branch name for the current/specified task."
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
@@ -23,21 +23,21 @@ module Abt
|
|
23
23
|
private
|
24
24
|
|
25
25
|
def name
|
26
|
-
task[
|
26
|
+
task["name"].downcase.gsub(/[^\w]+/, "-").gsub(/(^-|-$)/, "")
|
27
27
|
end
|
28
28
|
|
29
29
|
def ensure_current_is_valid!
|
30
|
-
abort
|
30
|
+
abort("Invalid task gid: #{task_gid}") if task.nil?
|
31
31
|
|
32
|
-
return if task[
|
32
|
+
return if task["memberships"].any? { |m| m.dig("project", "gid") == project_gid }
|
33
33
|
|
34
|
-
abort
|
34
|
+
abort("Invalid or unmatching project gid: #{project_gid}")
|
35
35
|
end
|
36
36
|
|
37
37
|
def task
|
38
38
|
@task ||= begin
|
39
|
-
warn
|
40
|
-
api.get("tasks/#{task_gid}", opt_fields:
|
39
|
+
warn("Fetching task...")
|
40
|
+
api.get("tasks/#{task_gid}", opt_fields: "name,memberships.project")
|
41
41
|
rescue Abt::HttpError::NotFoundError
|
42
42
|
nil
|
43
43
|
end
|
@@ -6,29 +6,28 @@ module Abt
|
|
6
6
|
module Commands
|
7
7
|
class Clear < BaseCommand
|
8
8
|
def self.usage
|
9
|
-
|
9
|
+
"abt clear asana"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Clear asana configuration"
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.flags
|
17
17
|
[
|
18
|
-
[
|
19
|
-
|
18
|
+
["-g", "--global",
|
19
|
+
"Clear global instead of local asana configuration (credentials etc.)"],
|
20
|
+
["-a", "--all", "Clear all asana 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
|
30
|
+
warn("Configuration cleared")
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
@@ -6,22 +6,21 @@ module Abt
|
|
6
6
|
module Commands
|
7
7
|
class Current < BaseCommand
|
8
8
|
def self.usage
|
9
|
-
|
9
|
+
"abt current asana[:<project-gid>[/<task-gid>]]"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Get or set project and or task for current git repository"
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
|
-
|
18
|
-
|
17
|
+
require_local_config!
|
19
18
|
require_project!
|
20
19
|
ensure_valid_configuration!
|
21
20
|
|
22
21
|
if path != config.path
|
23
22
|
config.path = path
|
24
|
-
warn
|
23
|
+
warn("Configuration updated")
|
25
24
|
end
|
26
25
|
|
27
26
|
print_configuration
|
@@ -34,25 +33,25 @@ module Abt
|
|
34
33
|
end
|
35
34
|
|
36
35
|
def ensure_valid_configuration!
|
37
|
-
abort
|
38
|
-
abort
|
36
|
+
abort("Invalid project: #{project_gid}") if project.nil?
|
37
|
+
abort("Invalid task: #{task_gid}") if task_gid && task.nil?
|
39
38
|
end
|
40
39
|
|
41
40
|
def project
|
42
41
|
@project ||= begin
|
43
|
-
warn
|
44
|
-
api.get("projects/#{project_gid}", opt_fields:
|
45
|
-
|
46
|
-
|
42
|
+
warn("Fetching project...")
|
43
|
+
api.get("projects/#{project_gid}", opt_fields: "name,permalink_url")
|
44
|
+
rescue Abt::HttpError::NotFoundError
|
45
|
+
nil
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
50
49
|
def task
|
51
50
|
@task ||= begin
|
52
|
-
warn
|
53
|
-
api.get("tasks/#{task_gid}", opt_fields:
|
54
|
-
|
55
|
-
|
51
|
+
warn("Fetching task...")
|
52
|
+
api.get("tasks/#{task_gid}", opt_fields: "name,permalink_url")
|
53
|
+
rescue Abt::HttpError::NotFoundError
|
54
|
+
nil
|
56
55
|
end
|
57
56
|
end
|
58
57
|
end
|
@@ -6,47 +6,49 @@ module Abt
|
|
6
6
|
module Commands
|
7
7
|
class Finalize < BaseCommand
|
8
8
|
def self.usage
|
9
|
-
|
9
|
+
"abt finalize asana[:<project-gid>/<task-gid>]"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Move current/specified task to section (column) for finalized tasks"
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
|
-
unless config.
|
18
|
-
abort 'This is a no-op for tasks outside the current project'
|
19
|
-
end
|
17
|
+
abort("This is a no-op for tasks outside the current project") unless project_gid == config.path.project_gid
|
20
18
|
require_task!
|
21
19
|
print_task(project_gid, task)
|
22
20
|
|
21
|
+
maybe_move_task
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def maybe_move_task
|
23
27
|
if task_already_in_finalized_section?
|
24
|
-
warn
|
28
|
+
warn("Task already in section: #{current_task_section['name']}")
|
25
29
|
else
|
26
|
-
warn
|
30
|
+
warn("Moving task to section: #{finalized_section['name']}")
|
27
31
|
move_task
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
31
|
-
private
|
32
|
-
|
33
35
|
def task_already_in_finalized_section?
|
34
36
|
!task_section_membership.nil?
|
35
37
|
end
|
36
38
|
|
37
39
|
def current_task_section
|
38
|
-
task_section_membership&.dig(
|
40
|
+
task_section_membership&.dig("section")
|
39
41
|
end
|
40
42
|
|
41
43
|
def task_section_membership
|
42
|
-
task[
|
43
|
-
membership.dig(
|
44
|
+
task["memberships"].find do |membership|
|
45
|
+
membership.dig("section", "gid") == config.finalized_section_gid
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
47
49
|
def finalized_section
|
48
50
|
@finalized_section ||= api.get("sections/#{config.finalized_section_gid}",
|
49
|
-
opt_fields:
|
51
|
+
opt_fields: "name")
|
50
52
|
end
|
51
53
|
|
52
54
|
def move_task
|
@@ -57,11 +59,8 @@ module Abt
|
|
57
59
|
|
58
60
|
def task
|
59
61
|
@task ||= begin
|
60
|
-
|
61
|
-
|
62
|
-
else
|
63
|
-
api.get("tasks/#{task_gid}", opt_fields: 'name,memberships.section.name,permalink_url')
|
64
|
-
end
|
62
|
+
api.get("tasks/#{task_gid}",
|
63
|
+
opt_fields: "name,memberships.section.name,permalink_url")
|
65
64
|
end
|
66
65
|
end
|
67
66
|
end
|
@@ -6,45 +6,47 @@ module Abt
|
|
6
6
|
module Commands
|
7
7
|
class HarvestTimeEntryData < BaseCommand
|
8
8
|
def self.usage
|
9
|
-
|
9
|
+
"abt harvest-time-entry-data asana[:<project-gid>/<task-gid>]"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Print Harvest time entry data for Asana task as json. Used by harvest start script."
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
17
|
require_task!
|
18
18
|
ensure_current_is_valid!
|
19
19
|
|
20
|
-
body
|
21
|
-
|
20
|
+
puts Oj.dump(body, mode: :json)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def body
|
26
|
+
{
|
27
|
+
notes: task["name"],
|
22
28
|
external_reference: {
|
23
29
|
id: task_gid.to_i,
|
24
30
|
group_id: project_gid.to_i,
|
25
|
-
permalink: task[
|
31
|
+
permalink: task["permalink_url"]
|
26
32
|
}
|
27
33
|
}
|
28
|
-
|
29
|
-
puts Oj.dump(body, mode: :json)
|
30
34
|
end
|
31
35
|
|
32
|
-
private
|
33
|
-
|
34
36
|
def ensure_current_is_valid!
|
35
|
-
abort
|
37
|
+
abort("Invalid task gid: #{task_gid}") if task.nil?
|
36
38
|
|
37
|
-
return if task[
|
39
|
+
return if task["memberships"].any? { |m| m.dig("project", "gid") == project_gid }
|
38
40
|
|
39
|
-
abort
|
41
|
+
abort("Invalid or unmatching project gid: #{project_gid}")
|
40
42
|
end
|
41
43
|
|
42
44
|
def task
|
43
45
|
@task ||= begin
|
44
|
-
warn
|
45
|
-
api.get("tasks/#{task_gid}", opt_fields:
|
46
|
-
|
47
|
-
|
46
|
+
warn("Fetching task...")
|
47
|
+
api.get("tasks/#{task_gid}", opt_fields: "name,permalink_url,memberships.project")
|
48
|
+
rescue Abt::HttpError::NotFoundError
|
49
|
+
nil
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|