abt-cli 0.0.14 → 0.0.19
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 +1 -1
- data/lib/abt.rb +4 -3
- 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 +89 -54
- data/lib/abt/cli/arguments_parser.rb +48 -0
- data/lib/abt/cli/{dialogs.rb → prompt.rb} +38 -18
- data/lib/abt/docs.rb +35 -28
- data/lib/abt/docs/cli.rb +42 -11
- data/lib/abt/docs/markdown.rb +38 -11
- data/lib/abt/git_config.rb +26 -31
- data/lib/abt/providers/asana/base_command.rb +17 -37
- data/lib/abt/providers/asana/commands/add.rb +15 -13
- data/lib/abt/providers/asana/commands/{branch-name.rb → branch_name.rb} +12 -7
- data/lib/abt/providers/asana/commands/clear.rb +19 -6
- data/lib/abt/providers/asana/commands/current.rb +22 -37
- data/lib/abt/providers/asana/commands/finalize.rb +6 -6
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +12 -7
- data/lib/abt/providers/asana/commands/init.rb +11 -11
- data/lib/abt/providers/asana/commands/pick.rb +30 -17
- data/lib/abt/providers/asana/commands/projects.rb +4 -4
- data/lib/abt/providers/asana/commands/share.rb +5 -9
- data/lib/abt/providers/asana/commands/start.rb +27 -19
- data/lib/abt/providers/asana/commands/tasks.rb +7 -6
- data/lib/abt/providers/asana/configuration.rb +23 -37
- data/lib/abt/providers/asana/path.rb +36 -0
- data/lib/abt/providers/devops/api.rb +12 -0
- data/lib/abt/providers/devops/base_command.rb +18 -44
- data/lib/abt/providers/devops/commands/boards.rb +7 -5
- data/lib/abt/providers/devops/commands/{branch-name.rb → branch_name.rb} +10 -6
- data/lib/abt/providers/devops/commands/clear.rb +19 -6
- data/lib/abt/providers/devops/commands/current.rb +17 -41
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +12 -4
- data/lib/abt/providers/devops/commands/init.rb +20 -20
- data/lib/abt/providers/devops/commands/pick.rb +18 -18
- data/lib/abt/providers/devops/commands/share.rb +6 -7
- data/lib/abt/providers/devops/commands/work-items.rb +4 -4
- data/lib/abt/providers/devops/configuration.rb +20 -57
- data/lib/abt/providers/devops/path.rb +50 -0
- data/lib/abt/providers/git/commands/branch.rb +28 -28
- data/lib/abt/providers/harvest/base_command.rb +18 -36
- data/lib/abt/providers/harvest/commands/clear.rb +19 -6
- data/lib/abt/providers/harvest/commands/current.rb +27 -34
- data/lib/abt/providers/harvest/commands/init.rb +10 -11
- data/lib/abt/providers/harvest/commands/pick.rb +16 -9
- data/lib/abt/providers/harvest/commands/projects.rb +4 -4
- data/lib/abt/providers/harvest/commands/share.rb +7 -11
- data/lib/abt/providers/harvest/commands/start.rb +6 -42
- data/lib/abt/providers/harvest/commands/stop.rb +10 -10
- data/lib/abt/providers/harvest/commands/tasks.rb +7 -4
- data/lib/abt/providers/harvest/commands/track.rb +66 -21
- data/lib/abt/providers/harvest/configuration.rb +23 -38
- data/lib/abt/providers/harvest/path.rb +36 -0
- data/lib/abt/version.rb +1 -1
- metadata +12 -9
- data/lib/abt/cli/io.rb +0 -23
- data/lib/abt/providers/asana/commands/clear_global.rb +0 -24
- data/lib/abt/providers/devops/commands/clear_global.rb +0 -24
- data/lib/abt/providers/harvest/commands/clear_global.rb +0 -24
@@ -5,19 +5,19 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class BranchName < BaseCommand
|
8
|
-
def self.
|
9
|
-
'branch-name asana[:<project-gid>/<task-gid>]'
|
8
|
+
def self.usage
|
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
|
-
def
|
16
|
+
def perform
|
17
17
|
require_task!
|
18
18
|
ensure_current_is_valid!
|
19
19
|
|
20
|
-
|
20
|
+
puts name
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
@@ -27,15 +27,20 @@ module Abt
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def ensure_current_is_valid!
|
30
|
-
|
30
|
+
abort "Invalid task gid: #{task_gid}" if task.nil?
|
31
31
|
|
32
32
|
return if task['memberships'].any? { |m| m.dig('project', 'gid') == project_gid }
|
33
33
|
|
34
|
-
|
34
|
+
abort "Invalid or unmatching project gid: #{project_gid}"
|
35
35
|
end
|
36
36
|
|
37
37
|
def task
|
38
|
-
@task ||=
|
38
|
+
@task ||= begin
|
39
|
+
warn 'Fetching task...'
|
40
|
+
api.get("tasks/#{task_gid}", opt_fields: 'name,memberships.project')
|
41
|
+
rescue Abt::HttpError::NotFoundError
|
42
|
+
nil
|
43
|
+
end
|
39
44
|
end
|
40
45
|
end
|
41
46
|
end
|
@@ -5,17 +5,30 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class Clear < BaseCommand
|
8
|
-
def self.
|
9
|
-
'clear asana'
|
8
|
+
def self.usage
|
9
|
+
'abt clear asana'
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
'Clear
|
13
|
+
'Clear asana configuration'
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
16
|
+
def self.flags
|
17
|
+
[
|
18
|
+
['-g', '--global', 'Clear global instead of local asana configuration (credentials etc.)'],
|
19
|
+
['-a', '--all', 'Clear all asana configuration']
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform
|
24
|
+
if flags[:global] && flags[:all]
|
25
|
+
abort('Flags --global and --all cannot be used at the same time')
|
26
|
+
end
|
27
|
+
|
28
|
+
config.clear_local unless flags[:global]
|
29
|
+
config.clear_global if flags[:global] || flags[:all]
|
30
|
+
|
31
|
+
warn 'Configuration cleared'
|
19
32
|
end
|
20
33
|
end
|
21
34
|
end
|
@@ -5,69 +5,54 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class Current < BaseCommand
|
8
|
-
def self.
|
9
|
-
'current asana[:<project-gid>[/<task-gid>]]'
|
8
|
+
def self.usage
|
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
|
-
def
|
16
|
+
def perform
|
17
|
+
abort 'Must be run inside a git repository' unless config.local_available?
|
18
|
+
|
17
19
|
require_project!
|
20
|
+
ensure_valid_configuration!
|
18
21
|
|
19
|
-
if
|
20
|
-
|
21
|
-
|
22
|
-
cli.warn 'Updating configuration'
|
23
|
-
update_configuration
|
22
|
+
if path != config.path
|
23
|
+
config.path = path
|
24
|
+
warn 'Configuration updated'
|
24
25
|
end
|
25
|
-
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
def show_current_configuration
|
30
|
-
if task_gid.nil?
|
31
|
-
print_project(project)
|
32
|
-
else
|
33
|
-
print_task(project, task)
|
34
|
-
end
|
27
|
+
print_configuration
|
35
28
|
end
|
36
29
|
|
37
|
-
|
38
|
-
ensure_project_is_valid!
|
39
|
-
config.project_gid = project_gid
|
40
|
-
|
41
|
-
if task_gid.nil?
|
42
|
-
print_project(project)
|
43
|
-
config.task_gid = nil
|
44
|
-
else
|
45
|
-
ensure_task_is_valid!
|
46
|
-
config.task_gid = task_gid
|
47
|
-
|
48
|
-
print_task(project, task)
|
49
|
-
end
|
50
|
-
end
|
30
|
+
private
|
51
31
|
|
52
|
-
def
|
53
|
-
|
32
|
+
def print_configuration
|
33
|
+
task_gid.nil? ? print_project(project) : print_task(project, task)
|
54
34
|
end
|
55
35
|
|
56
|
-
def
|
57
|
-
|
36
|
+
def ensure_valid_configuration!
|
37
|
+
abort "Invalid project: #{project_gid}" if project.nil?
|
38
|
+
abort "Invalid task: #{task_gid}" if task_gid && task.nil?
|
58
39
|
end
|
59
40
|
|
60
41
|
def project
|
61
42
|
@project ||= begin
|
62
|
-
|
43
|
+
warn 'Fetching project...'
|
63
44
|
api.get("projects/#{project_gid}", opt_fields: 'name,permalink_url')
|
45
|
+
rescue Abt::HttpError::NotFoundError
|
46
|
+
nil
|
64
47
|
end
|
65
48
|
end
|
66
49
|
|
67
50
|
def task
|
68
51
|
@task ||= begin
|
69
|
-
|
52
|
+
warn 'Fetching task...'
|
70
53
|
api.get("tasks/#{task_gid}", opt_fields: 'name,permalink_url')
|
54
|
+
rescue Abt::HttpError::NotFoundError
|
55
|
+
nil
|
71
56
|
end
|
72
57
|
end
|
73
58
|
end
|
@@ -5,25 +5,25 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class Finalize < BaseCommand
|
8
|
-
def self.
|
9
|
-
'finalize asana[:<project-gid>/<task-gid>]'
|
8
|
+
def self.usage
|
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
|
-
def
|
16
|
+
def perform
|
17
17
|
unless config.local_available?
|
18
|
-
|
18
|
+
abort 'This is a no-op for tasks outside the current project'
|
19
19
|
end
|
20
20
|
require_task!
|
21
21
|
print_task(project_gid, task)
|
22
22
|
|
23
23
|
if task_already_in_finalized_section?
|
24
|
-
|
24
|
+
warn "Task already in section: #{current_task_section['name']}"
|
25
25
|
else
|
26
|
-
|
26
|
+
warn "Moving task to section: #{finalized_section['name']}"
|
27
27
|
move_task
|
28
28
|
end
|
29
29
|
end
|
@@ -5,15 +5,15 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class HarvestTimeEntryData < BaseCommand
|
8
|
-
def self.
|
9
|
-
'harvest-time-entry-data asana[:<project-gid>/<task-gid>]'
|
8
|
+
def self.usage
|
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
|
-
def
|
16
|
+
def perform
|
17
17
|
require_task!
|
18
18
|
ensure_current_is_valid!
|
19
19
|
|
@@ -26,21 +26,26 @@ module Abt
|
|
26
26
|
}
|
27
27
|
}
|
28
28
|
|
29
|
-
|
29
|
+
puts Oj.dump(body, mode: :json)
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def ensure_current_is_valid!
|
35
|
-
|
35
|
+
abort "Invalid task gid: #{task_gid}" if task.nil?
|
36
36
|
|
37
37
|
return if task['memberships'].any? { |m| m.dig('project', 'gid') == project_gid }
|
38
38
|
|
39
|
-
|
39
|
+
abort "Invalid or unmatching project gid: #{project_gid}"
|
40
40
|
end
|
41
41
|
|
42
42
|
def task
|
43
|
-
@task ||=
|
43
|
+
@task ||= begin
|
44
|
+
warn 'Fetching task...'
|
45
|
+
api.get("tasks/#{task_gid}", opt_fields: 'name,permalink_url,memberships.project')
|
46
|
+
rescue Abt::HttpError::NotFoundError
|
47
|
+
nil
|
48
|
+
end
|
44
49
|
end
|
45
50
|
end
|
46
51
|
end
|
@@ -5,8 +5,8 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class Init < BaseCommand
|
8
|
-
def self.
|
9
|
-
'init asana'
|
8
|
+
def self.usage
|
9
|
+
'abt init asana'
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
@@ -18,13 +18,13 @@ module Abt
|
|
18
18
|
@cli = cli
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
|
21
|
+
def perform
|
22
|
+
abort 'Must be run inside a git repository' unless config.local_available?
|
23
23
|
|
24
24
|
projects # Load projects up front to make it obvious that searches are instant
|
25
25
|
project = find_search_result
|
26
26
|
|
27
|
-
config.
|
27
|
+
config.path = Path.from_ids(project['gid'])
|
28
28
|
|
29
29
|
print_project(project)
|
30
30
|
end
|
@@ -32,17 +32,17 @@ module Abt
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def find_search_result
|
35
|
-
|
35
|
+
warn 'Select a project'
|
36
36
|
|
37
37
|
loop do
|
38
|
-
matches = matches_for_string cli.prompt('Enter search')
|
38
|
+
matches = matches_for_string cli.prompt.text('Enter search')
|
39
39
|
if matches.empty?
|
40
|
-
|
40
|
+
warn 'No matches'
|
41
41
|
next
|
42
42
|
end
|
43
43
|
|
44
|
-
|
45
|
-
choice = cli.
|
44
|
+
warn 'Showing the 10 first matches' if matches.size > 10
|
45
|
+
choice = cli.prompt.choice 'Select a project', matches[0...10], true
|
46
46
|
break choice unless choice.nil?
|
47
47
|
end
|
48
48
|
end
|
@@ -61,7 +61,7 @@ module Abt
|
|
61
61
|
|
62
62
|
def projects
|
63
63
|
@projects ||= begin
|
64
|
-
|
64
|
+
warn 'Fetching projects...'
|
65
65
|
api.get_paged('projects',
|
66
66
|
workspace: config.workspace_gid,
|
67
67
|
archived: false,
|
@@ -5,60 +5,73 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class Pick < BaseCommand
|
8
|
-
def self.
|
9
|
-
'pick asana[:<project-gid>]'
|
8
|
+
def self.usage
|
9
|
+
'abt pick asana[:<project-gid>]'
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
13
|
'Pick task for current git repository'
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
16
|
+
def self.flags
|
17
|
+
[
|
18
|
+
['-d', '--dry-run', 'Keep existing configuration']
|
19
|
+
]
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform
|
23
|
+
abort 'Must be run inside a git repository' unless config.local_available?
|
18
24
|
require_project!
|
19
25
|
|
20
|
-
|
26
|
+
warn project['name']
|
21
27
|
|
22
28
|
task = select_task
|
23
29
|
|
24
|
-
config.project_gid = project_gid # We might have gotten the project ID as an argument
|
25
|
-
config.task_gid = task['gid']
|
26
|
-
|
27
30
|
print_task(project, task)
|
31
|
+
|
32
|
+
return if flags[:"dry-run"]
|
33
|
+
|
34
|
+
config.path = Path.from_ids(project_gid, task['gid'])
|
28
35
|
end
|
29
36
|
|
30
37
|
private
|
31
38
|
|
32
39
|
def project
|
33
|
-
@project ||= api.get("projects/#{project_gid}")
|
40
|
+
@project ||= api.get("projects/#{project_gid}", opt_fields: 'name')
|
34
41
|
end
|
35
42
|
|
36
43
|
def select_task
|
37
44
|
loop do
|
38
|
-
section = cli.
|
39
|
-
|
45
|
+
section = cli.prompt.choice 'Which section?', sections
|
46
|
+
warn 'Fetching tasks...'
|
40
47
|
tasks = tasks_in_section(section)
|
41
48
|
|
42
49
|
if tasks.length.zero?
|
43
|
-
|
50
|
+
warn 'Section is empty'
|
44
51
|
next
|
45
52
|
end
|
46
53
|
|
47
|
-
task = cli.
|
54
|
+
task = cli.prompt.choice 'Select a task', tasks, true
|
48
55
|
return task if task
|
49
56
|
end
|
50
57
|
end
|
51
58
|
|
52
59
|
def tasks_in_section(section)
|
53
|
-
api.get_paged(
|
60
|
+
tasks = api.get_paged(
|
61
|
+
'tasks',
|
62
|
+
section: section['gid'],
|
63
|
+
opt_fields: 'name,completed,permalink_url'
|
64
|
+
)
|
65
|
+
|
66
|
+
# The below filtering is the best we can do with Asanas api, see this:
|
67
|
+
# https://forum.asana.com/t/tasks-query-completed-since-is-broken-for-sections/21461
|
68
|
+
tasks.filter { |task| !task['completed'] }
|
54
69
|
end
|
55
70
|
|
56
71
|
def sections
|
57
72
|
@sections ||= begin
|
58
|
-
|
73
|
+
warn 'Fetching sections...'
|
59
74
|
api.get_paged("projects/#{project_gid}/sections", opt_fields: 'name')
|
60
|
-
rescue Abt::HttpError::HttpError
|
61
|
-
[]
|
62
75
|
end
|
63
76
|
end
|
64
77
|
end
|
@@ -5,15 +5,15 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class Projects < BaseCommand
|
8
|
-
def self.
|
9
|
-
'projects asana'
|
8
|
+
def self.usage
|
9
|
+
'abt projects asana'
|
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
|
-
def
|
16
|
+
def perform
|
17
17
|
projects.map do |project|
|
18
18
|
print_project(project)
|
19
19
|
end
|
@@ -23,7 +23,7 @@ module Abt
|
|
23
23
|
|
24
24
|
def projects
|
25
25
|
@projects ||= begin
|
26
|
-
|
26
|
+
warn 'Fetching projects...'
|
27
27
|
api.get_paged(
|
28
28
|
'projects',
|
29
29
|
workspace: config.workspace_gid,
|
@@ -5,22 +5,18 @@ module Abt
|
|
5
5
|
module Asana
|
6
6
|
module Commands
|
7
7
|
class Share < BaseCommand
|
8
|
-
def self.
|
9
|
-
'share asana[:<project-gid>[/<task-gid>]]'
|
8
|
+
def self.usage
|
9
|
+
'abt share asana[:<project-gid>[/<task-gid>]]'
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.description
|
13
|
-
'Print project/task
|
13
|
+
'Print project/task ARI'
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def perform
|
17
17
|
require_project!
|
18
18
|
|
19
|
-
|
20
|
-
cli.print_provider_command('asana', project_gid)
|
21
|
-
else
|
22
|
-
cli.print_provider_command('asana', "#{project_gid}/#{task_gid}")
|
23
|
-
end
|
19
|
+
cli.print_ari('asana', path)
|
24
20
|
end
|
25
21
|
end
|
26
22
|
end
|