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
@@ -6,74 +6,45 @@ module Abt
|
|
6
6
|
module Commands
|
7
7
|
class Init < BaseCommand
|
8
8
|
def self.usage
|
9
|
-
|
9
|
+
"abt init harvest"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
|
13
|
+
"Pick Harvest project for current git repository"
|
14
14
|
end
|
15
15
|
|
16
16
|
def perform
|
17
|
-
|
18
|
-
|
17
|
+
require_local_config!
|
19
18
|
projects # Load projects up front to make it obvious that searches are instant
|
20
|
-
project =
|
19
|
+
project = cli.prompt.search("Select a project", searchable_projects)["project"]
|
21
20
|
|
22
|
-
config.path = Path.from_ids(project[
|
21
|
+
config.path = Path.from_ids(project_id: project["id"])
|
23
22
|
|
24
23
|
print_project(project)
|
25
24
|
end
|
26
25
|
|
27
26
|
private
|
28
27
|
|
29
|
-
def find_search_result
|
30
|
-
warn 'Select a project'
|
31
|
-
|
32
|
-
loop do
|
33
|
-
matches = matches_for_string cli.prompt.text('Enter search')
|
34
|
-
if matches.empty?
|
35
|
-
warn 'No matches'
|
36
|
-
next
|
37
|
-
end
|
38
|
-
|
39
|
-
warn 'Showing the 10 first matches' if matches.size > 10
|
40
|
-
choice = cli.prompt.choice 'Select a project', matches[0...10], true
|
41
|
-
break choice['project'] unless choice.nil?
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def matches_for_string(string)
|
46
|
-
search_string = sanitize_string(string)
|
47
|
-
|
48
|
-
searchable_projects.select do |project|
|
49
|
-
sanitize_string(project['name']).include?(search_string)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def sanitize_string(string)
|
54
|
-
string.downcase.gsub(/[^\w]/, '')
|
55
|
-
end
|
56
|
-
|
57
28
|
def searchable_projects
|
58
29
|
@searchable_projects ||= projects.map do |project|
|
59
30
|
{
|
60
|
-
|
61
|
-
|
31
|
+
"name" => "#{project['client']['name']} > #{project['name']}",
|
32
|
+
"project" => project
|
62
33
|
}
|
63
34
|
end
|
64
35
|
end
|
65
36
|
|
66
37
|
def projects
|
67
38
|
@projects ||= begin
|
68
|
-
warn
|
39
|
+
warn("Fetching projects...")
|
69
40
|
project_assignments.map do |project_assignment|
|
70
|
-
project_assignment[
|
41
|
+
project_assignment["project"].merge("client" => project_assignment["client"])
|
71
42
|
end
|
72
43
|
end
|
73
44
|
end
|
74
45
|
|
75
46
|
def project_assignments
|
76
|
-
@project_assignments ||= api.get_paged(
|
47
|
+
@project_assignments ||= api.get_paged("users/me/project_assignments")
|
77
48
|
end
|
78
49
|
end
|
79
50
|
end
|
@@ -6,51 +6,55 @@ 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
|
+
require_local_config!
|
24
24
|
require_project!
|
25
25
|
|
26
|
-
warn
|
27
|
-
task =
|
26
|
+
warn(project["name"])
|
27
|
+
task = pick_task
|
28
28
|
|
29
29
|
print_task(project, task)
|
30
30
|
|
31
31
|
return if flags[:"dry-run"]
|
32
32
|
|
33
|
-
config.path = Path.from_ids(project_id, task[
|
33
|
+
config.path = Path.from_ids(project_id: project_id, task_id: task["id"])
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
38
|
def project
|
39
|
-
project_assignment[
|
39
|
+
project_assignment["project"]
|
40
|
+
end
|
41
|
+
|
42
|
+
def pick_task
|
43
|
+
cli.prompt.choice("Select a task", tasks)
|
40
44
|
end
|
41
45
|
|
42
46
|
def tasks
|
43
|
-
@tasks ||= project_assignment[
|
47
|
+
@tasks ||= project_assignment["task_assignments"].map { |ta| ta["task"] }
|
44
48
|
end
|
45
49
|
|
46
50
|
def project_assignment
|
47
51
|
@project_assignment ||= begin
|
48
|
-
project_assignments.find { |pa| pa[
|
52
|
+
project_assignments.find { |pa| pa["project"]["id"].to_s == project_id }
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
52
56
|
def project_assignments
|
53
|
-
@project_assignments ||= api.get_paged(
|
57
|
+
@project_assignments ||= api.get_paged("users/me/project_assignments")
|
54
58
|
end
|
55
59
|
end
|
56
60
|
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
|
-
warn
|
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,17 +6,19 @@ 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
|
-
|
18
|
-
|
19
|
-
cli.
|
17
|
+
if path != ""
|
18
|
+
cli.print_ari("harvest", path)
|
19
|
+
elsif cli.output.isatty
|
20
|
+
warn("No configuration for project. Did you initialize Harvest?")
|
21
|
+
end
|
20
22
|
end
|
21
23
|
end
|
22
24
|
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,19 +6,19 @@ 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
|
-
abort
|
17
|
+
abort("No running time entry") if time_entry.nil?
|
18
18
|
|
19
19
|
stop_time_entry
|
20
20
|
|
21
|
-
warn
|
21
|
+
warn("Harvest time entry stopped")
|
22
22
|
print_task(project, task)
|
23
23
|
end
|
24
24
|
|
@@ -27,28 +27,28 @@ module Abt
|
|
27
27
|
def stop_time_entry
|
28
28
|
api.patch("time_entries/#{time_entry['id']}/stop")
|
29
29
|
rescue Abt::HttpError::HttpError => e
|
30
|
-
warn
|
31
|
-
abort
|
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
|
-
warn
|
51
|
-
abort
|
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,24 +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
31
|
@tasks ||= begin
|
32
|
-
warn
|
33
|
-
project_assignment[
|
32
|
+
warn("Fetching tasks...")
|
33
|
+
project_assignment["task_assignments"].map { |ta| ta["task"] }
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
37
|
def project_assignment
|
38
38
|
@project_assignment ||= begin
|
39
|
-
project_assignments.find { |pa| pa[
|
39
|
+
project_assignments.find { |pa| pa["project"]["id"].to_s == project_id }
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
43
|
def project_assignments
|
44
|
-
@project_assignments ||= api.get_paged(
|
44
|
+
@project_assignments ||= api.get_paged("users/me/project_assignments")
|
45
45
|
end
|
46
46
|
end
|
47
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
|
-
abort
|
35
|
+
abort("Invalid task")
|
33
36
|
end
|
34
37
|
|
35
38
|
private
|
@@ -39,65 +42,77 @@ module Abt
|
|
39
42
|
end
|
40
43
|
|
41
44
|
def create_time_entry
|
42
|
-
body =
|
43
|
-
body.merge!(hours: flags[:time]) if flags.key? :time
|
45
|
+
body = time_entry_data
|
44
46
|
|
45
|
-
result = api.post(
|
47
|
+
result = api.post("time_entries", Oj.dump(body, mode: :json))
|
46
48
|
|
47
|
-
if flags.key?(:time) && flags[:running]
|
48
|
-
api.patch("time_entries/#{result['id']}/restart")
|
49
|
-
end
|
49
|
+
api.patch("time_entries/#{result['id']}/restart") if flags.key?(:time) && flags[:running]
|
50
50
|
|
51
51
|
result
|
52
52
|
end
|
53
53
|
|
54
|
+
def time_entry_data
|
55
|
+
body = time_entry_base_data
|
56
|
+
|
57
|
+
maybe_add_external_link(body)
|
58
|
+
maybe_add_comment(body)
|
59
|
+
maybe_add_time(body)
|
60
|
+
|
61
|
+
body
|
62
|
+
end
|
63
|
+
|
54
64
|
def time_entry_base_data
|
55
|
-
|
65
|
+
{
|
56
66
|
project_id: project_id,
|
57
67
|
task_id: task_id,
|
58
68
|
user_id: config.user_id,
|
59
69
|
spent_date: Date.today.iso8601
|
60
70
|
}
|
71
|
+
end
|
61
72
|
|
73
|
+
def maybe_add_external_link(body)
|
62
74
|
if external_link_data
|
63
|
-
warn
|
75
|
+
warn(<<~TXT)
|
64
76
|
Linking to:
|
65
|
-
|
66
|
-
|
77
|
+
#{external_link_data[:notes]}
|
78
|
+
#{external_link_data[:external_reference][:permalink]}
|
67
79
|
TXT
|
68
|
-
body.merge!
|
80
|
+
body.merge!(external_link_data)
|
69
81
|
else
|
70
|
-
warn
|
82
|
+
warn("No external link provided")
|
71
83
|
end
|
84
|
+
end
|
72
85
|
|
86
|
+
def maybe_add_comment(body)
|
73
87
|
body[:notes] = flags[:comment] if flags.key?(:comment)
|
74
|
-
body[:notes] ||= cli.prompt.text(
|
75
|
-
|
88
|
+
body[:notes] ||= cli.prompt.text("Fill in comment (optional)")
|
89
|
+
end
|
90
|
+
|
91
|
+
def maybe_add_time(body)
|
92
|
+
body[:hours] = flags[:time] if flags.key?(:time)
|
76
93
|
end
|
77
94
|
|
78
95
|
def external_link_data
|
79
|
-
@external_link_data
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
Oj.load(lines.first, symbol_keys: true)
|
90
|
-
end
|
96
|
+
return @external_link_data if instance_variable_defined?(:@external_link_data)
|
97
|
+
|
98
|
+
lines = fetch_link_data_lines
|
99
|
+
|
100
|
+
return @external_link_data = nil if lines.empty?
|
101
|
+
|
102
|
+
if lines.length > 1
|
103
|
+
abort("Got reference data from multiple scheme providers, only one is supported at a time")
|
91
104
|
end
|
105
|
+
|
106
|
+
@external_link_data = Oj.load(lines.first, symbol_keys: true)
|
92
107
|
end
|
93
108
|
|
94
|
-
def
|
109
|
+
def fetch_link_data_lines
|
95
110
|
other_aris = cli.aris - [ari]
|
96
111
|
return [] if other_aris.empty?
|
97
112
|
|
98
113
|
input = StringIO.new(other_aris.to_s)
|
99
114
|
output = StringIO.new
|
100
|
-
Abt::Cli.new(argv: [
|
115
|
+
Abt::Cli.new(argv: ["harvest-time-entry-data"], output: output, input: input).perform
|
101
116
|
|
102
117
|
output.string.strip.lines
|
103
118
|
end
|
@@ -108,7 +123,7 @@ module Abt
|
|
108
123
|
return unless config.local_available?
|
109
124
|
|
110
125
|
config.path = path
|
111
|
-
warn
|
126
|
+
warn("Current task updated")
|
112
127
|
end
|
113
128
|
end
|
114
129
|
end
|