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.
- checksums.yaml +4 -4
- data/bin/abt +3 -3
- data/lib/abt.rb +6 -6
- data/lib/abt/ari.rb +20 -0
- data/lib/abt/ari_list.rb +13 -0
- data/lib/abt/base_command.rb +63 -0
- data/lib/abt/cli.rb +58 -59
- data/lib/abt/cli/arguments_parser.rb +8 -27
- 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 +52 -20
- data/lib/abt/docs.rb +48 -25
- data/lib/abt/docs/cli.rb +7 -7
- data/lib/abt/docs/markdown.rb +13 -12
- data/lib/abt/git_config.rb +21 -39
- data/lib/abt/providers/asana/api.rb +9 -9
- data/lib/abt/providers/asana/base_command.rb +16 -38
- data/lib/abt/providers/asana/commands/add.rb +18 -15
- data/lib/abt/providers/asana/commands/branch_name.rb +13 -8
- data/lib/abt/providers/asana/commands/clear.rb +8 -7
- data/lib/abt/providers/asana/commands/current.rb +23 -38
- data/lib/abt/providers/asana/commands/finalize.rb +11 -16
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +14 -9
- data/lib/abt/providers/asana/commands/init.rb +8 -41
- data/lib/abt/providers/asana/commands/pick.rb +22 -17
- data/lib/abt/providers/asana/commands/projects.rb +5 -5
- data/lib/abt/providers/asana/commands/share.rb +6 -8
- data/lib/abt/providers/asana/commands/start.rb +26 -23
- data/lib/abt/providers/asana/commands/tasks.rb +6 -5
- data/lib/abt/providers/asana/configuration.rb +34 -40
- data/lib/abt/providers/asana/path.rb +36 -0
- data/lib/abt/providers/devops/api.rb +23 -11
- data/lib/abt/providers/devops/base_command.rb +18 -43
- data/lib/abt/providers/devops/commands/boards.rb +5 -7
- data/lib/abt/providers/devops/commands/branch_name.rb +14 -10
- data/lib/abt/providers/devops/commands/clear.rb +8 -7
- data/lib/abt/providers/devops/commands/current.rb +25 -49
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +20 -12
- data/lib/abt/providers/devops/commands/init.rb +29 -25
- data/lib/abt/providers/devops/commands/pick.rb +11 -18
- data/lib/abt/providers/devops/commands/share.rb +7 -6
- data/lib/abt/providers/devops/commands/{work-items.rb → work_items.rb} +3 -3
- data/lib/abt/providers/devops/configuration.rb +31 -56
- data/lib/abt/providers/devops/path.rb +51 -0
- data/lib/abt/providers/git/commands/branch.rb +29 -25
- data/lib/abt/providers/harvest/api.rb +8 -8
- data/lib/abt/providers/harvest/base_command.rb +18 -38
- data/lib/abt/providers/harvest/commands/clear.rb +8 -7
- data/lib/abt/providers/harvest/commands/current.rb +28 -35
- data/lib/abt/providers/harvest/commands/init.rb +10 -39
- data/lib/abt/providers/harvest/commands/pick.rb +11 -12
- data/lib/abt/providers/harvest/commands/projects.rb +5 -5
- data/lib/abt/providers/harvest/commands/share.rb +6 -8
- data/lib/abt/providers/harvest/commands/start.rb +5 -3
- data/lib/abt/providers/harvest/commands/stop.rb +13 -13
- data/lib/abt/providers/harvest/commands/tasks.rb +9 -6
- data/lib/abt/providers/harvest/commands/track.rb +40 -32
- data/lib/abt/providers/harvest/configuration.rb +28 -37
- data/lib/abt/providers/harvest/path.rb +36 -0
- data/lib/abt/version.rb +1 -1
- metadata +18 -6
- 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
|
-
|
9
|
+
"abt pick harvest[:<project-id>]"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Pick task for current git repository"
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.flags
|
17
17
|
[
|
18
|
-
[
|
18
|
+
["-d", "--dry-run", "Keep existing configuration"]
|
19
19
|
]
|
20
20
|
end
|
21
21
|
|
22
22
|
def perform
|
23
|
-
|
23
|
+
abort("Must be run inside a git repository") unless config.local_available?
|
24
24
|
require_project!
|
25
25
|
|
26
|
-
|
27
|
-
task = cli.prompt.choice
|
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.
|
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[
|
39
|
+
project_assignment["project"]
|
41
40
|
end
|
42
41
|
|
43
42
|
def tasks
|
44
|
-
@tasks ||= project_assignment[
|
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[
|
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(
|
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
|
-
|
9
|
+
"abt projects harvest"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
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
|
-
|
26
|
+
warn("Fetching projects...")
|
27
27
|
project_assignments.map do |project_assignment|
|
28
|
-
project_assignment[
|
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(
|
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
|
-
|
9
|
+
"abt share harvest[:<project-id>[/<task-id>]]"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Print project/task ARI"
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
|
-
if
|
18
|
-
cli.
|
19
|
-
elsif
|
20
|
-
|
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
|
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
|
-
|
11
|
+
"abt start harvest[:<project-id>/<task-id>] [options]"
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.description
|
15
|
-
|
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
|
-
|
9
|
+
"abt stop harvest"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Stop running harvest tracker"
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
|
-
|
17
|
+
abort("No running time entry") if time_entry.nil?
|
18
18
|
|
19
19
|
stop_time_entry
|
20
20
|
|
21
|
-
|
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[
|
35
|
+
time_entry["project"]
|
36
36
|
end
|
37
37
|
|
38
38
|
def task
|
39
|
-
time_entry[
|
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
|
-
|
45
|
+
"time_entries",
|
46
46
|
is_running: true,
|
47
47
|
user_id: config.user_id
|
48
48
|
).first
|
49
|
-
rescue Abt::HttpError::HttpError => e
|
50
|
-
|
51
|
-
|
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
|
-
|
9
|
+
"abt tasks harvest"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
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[
|
27
|
+
project_assignment["project"]
|
28
28
|
end
|
29
29
|
|
30
30
|
def tasks
|
31
|
-
@tasks ||=
|
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[
|
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(
|
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
|
-
|
9
|
+
"abt track harvest[:<project-id>/<task-id>] [options]"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
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
|
-
[
|
19
|
-
[
|
20
|
-
[
|
21
|
-
|
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[
|
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
|
-
|
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
|
46
|
+
body[:hours] = flags[:time] if flags.key?(:time)
|
44
47
|
|
45
|
-
result = api.post(
|
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
|
-
|
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!
|
69
|
+
body.merge!(external_link_data)
|
69
70
|
else
|
70
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
108
|
+
return if path == config.path
|
100
109
|
return unless config.local_available?
|
101
110
|
|
102
|
-
|
103
|
-
|
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
|
-
|
14
|
+
git.available?
|
16
15
|
end
|
17
16
|
|
18
|
-
def
|
19
|
-
local_available?
|
17
|
+
def path
|
18
|
+
Path.new(local_available? && git["path"] || "")
|
20
19
|
end
|
21
20
|
|
22
|
-
def
|
23
|
-
|
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
|
-
|
30
|
+
git_global.clear(output: verbose ? cli.err_output : nil)
|
45
31
|
end
|
46
32
|
|
47
33
|
def access_token
|
48
|
-
return
|
34
|
+
return git_global["accessToken"] unless git_global["accessToken"].nil?
|
49
35
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
45
|
+
return git_global["accountId"] unless git_global["accountId"].nil?
|
60
46
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
56
|
+
return git_global["userId"] unless git_global["userId"].nil?
|
71
57
|
|
72
|
-
|
58
|
+
git_global["userId"] = api.get("users/me")["id"].to_s
|
73
59
|
end
|
74
60
|
|
75
61
|
private
|
76
62
|
|
77
|
-
|
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
|