abt-cli 0.0.7 → 0.0.12
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 -0
- data/lib/abt/cli.rb +5 -5
- data/lib/abt/cli/dialogs.rb +36 -14
- data/lib/abt/docs.rb +4 -0
- data/lib/abt/git_config.rb +13 -0
- data/lib/abt/providers/asana/base_command.rb +11 -0
- data/lib/abt/providers/asana/commands/add.rb +75 -0
- data/lib/abt/providers/asana/commands/current.rb +3 -3
- data/lib/abt/providers/asana/commands/finalize.rb +1 -1
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +3 -4
- data/lib/abt/providers/asana/commands/init.rb +5 -0
- data/lib/abt/providers/asana/commands/pick.rb +17 -4
- data/lib/abt/providers/asana/commands/share.rb +3 -3
- data/lib/abt/providers/asana/commands/start.rb +1 -1
- data/lib/abt/providers/asana/commands/tasks.rb +2 -0
- data/lib/abt/providers/asana/configuration.rb +9 -3
- data/lib/abt/providers/devops.rb +19 -0
- data/lib/abt/providers/devops/api.rb +77 -0
- data/lib/abt/providers/devops/base_command.rb +97 -0
- data/lib/abt/providers/devops/commands/boards.rb +34 -0
- data/lib/abt/providers/devops/commands/clear.rb +24 -0
- data/lib/abt/providers/devops/commands/clear_global.rb +24 -0
- data/lib/abt/providers/devops/commands/current.rb +93 -0
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +53 -0
- data/lib/abt/providers/devops/commands/init.rb +72 -0
- data/lib/abt/providers/devops/commands/pick.rb +78 -0
- data/lib/abt/providers/devops/commands/share.rb +26 -0
- data/lib/abt/providers/devops/commands/work-items.rb +46 -0
- data/lib/abt/providers/devops/configuration.rb +110 -0
- data/lib/abt/providers/harvest/base_command.rb +11 -0
- data/lib/abt/providers/harvest/commands/current.rb +3 -3
- data/lib/abt/providers/harvest/commands/pick.rb +1 -0
- data/lib/abt/providers/harvest/commands/start.rb +21 -65
- data/lib/abt/providers/harvest/commands/tasks.rb +2 -0
- data/lib/abt/providers/harvest/commands/track.rb +72 -0
- data/lib/abt/providers/harvest/configuration.rb +4 -3
- data/lib/abt/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f1631b9069ca5094ba301eb3ccc5cf42da8e48a85b2678e8499cea1e9e4551c
|
4
|
+
data.tar.gz: cd9dc57e8a6276de5a2bca61e59fb537bc8887901615d7ec86f2c4a59145f76c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdca57213f85db7a3604ac1a5ed50a16a2918fded5b3b3e090e52aeb184414345d7609f08ef5145f205cf3fb9f1576f9e0aaa6b25a2e41684cfd506bed19e851
|
7
|
+
data.tar.gz: d7eb95a22516aa0f894262e0a1b52ee659736b88775936040a87db03260711e12dbc6764674799b5d2f8bdb86cb14495f01440a0703d300759c3482db17a810a
|
data/bin/abt
CHANGED
data/lib/abt/cli.rb
CHANGED
@@ -20,7 +20,7 @@ module Abt
|
|
20
20
|
@output = output
|
21
21
|
@err_output = err_output
|
22
22
|
|
23
|
-
@args +=
|
23
|
+
@args += args_from_input unless input.isatty # Add piped arguments
|
24
24
|
end
|
25
25
|
|
26
26
|
def perform
|
@@ -57,13 +57,13 @@ module Abt
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
def
|
61
|
-
|
60
|
+
def args_from_input
|
61
|
+
input_string = input.read
|
62
62
|
|
63
|
-
|
63
|
+
abort 'No input from pipe' if input_string.nil? || input_string.empty?
|
64
64
|
|
65
65
|
# Exclude comment part of piped input lines
|
66
|
-
lines_without_comments =
|
66
|
+
lines_without_comments = input_string.lines.map do |line|
|
67
67
|
line.split(' # ').first
|
68
68
|
end
|
69
69
|
|
data/lib/abt/cli/dialogs.rb
CHANGED
@@ -24,16 +24,18 @@ module Abt
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def prompt_choice(text, options,
|
28
|
-
if options.one?
|
29
|
-
warn "Selected: #{options.first['name']}"
|
30
|
-
return options.first
|
31
|
-
end
|
32
|
-
|
27
|
+
def prompt_choice(text, options, nil_option = false)
|
33
28
|
warn "#{text}:"
|
34
29
|
|
30
|
+
if options.length.zero?
|
31
|
+
abort 'No available options' unless nil_option
|
32
|
+
|
33
|
+
warn 'No available options'
|
34
|
+
return nil
|
35
|
+
end
|
36
|
+
|
35
37
|
print_options(options)
|
36
|
-
select_options(options,
|
38
|
+
select_options(options, nil_option)
|
37
39
|
end
|
38
40
|
|
39
41
|
private
|
@@ -44,12 +46,13 @@ module Abt
|
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
47
|
-
def select_options(options,
|
48
|
-
|
49
|
+
def select_options(options, nil_option)
|
50
|
+
loop do
|
51
|
+
number = read_option_number(options.length, nil_option)
|
49
52
|
if number.nil?
|
50
|
-
return nil if
|
53
|
+
return nil if nil_option
|
51
54
|
|
52
|
-
|
55
|
+
next
|
53
56
|
end
|
54
57
|
|
55
58
|
option = options[number - 1]
|
@@ -59,12 +62,12 @@ module Abt
|
|
59
62
|
end
|
60
63
|
end
|
61
64
|
|
62
|
-
def read_option_number(options_length,
|
63
|
-
err_output.print "(1-#{options_length}#{
|
65
|
+
def read_option_number(options_length, nil_option)
|
66
|
+
err_output.print "(1-#{options_length}#{nil_option_string(nil_option)}): "
|
64
67
|
|
65
68
|
input = read_user_input
|
66
69
|
|
67
|
-
return nil if
|
70
|
+
return nil if nil_option && input == nil_option_character(nil_option)
|
68
71
|
|
69
72
|
option_number = input.to_i
|
70
73
|
if option_number <= 0 || option_number > options_length
|
@@ -75,6 +78,25 @@ module Abt
|
|
75
78
|
option_number
|
76
79
|
end
|
77
80
|
|
81
|
+
def nil_option_string(nil_option)
|
82
|
+
return '' unless nil_option
|
83
|
+
|
84
|
+
", #{nil_option_character(nil_option)}: #{nil_option_description(nil_option)}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def nil_option_character(nil_option)
|
88
|
+
return 'q' if nil_option == true
|
89
|
+
|
90
|
+
nil_option[0]
|
91
|
+
end
|
92
|
+
|
93
|
+
def nil_option_description(nil_option)
|
94
|
+
return 'back' if nil_option == true
|
95
|
+
return nil_option if nil_option.is_a?(String)
|
96
|
+
|
97
|
+
nil_option[1]
|
98
|
+
end
|
99
|
+
|
78
100
|
def read_user_input
|
79
101
|
open('/dev/tty', &:gets).strip
|
80
102
|
end
|
data/lib/abt/docs.rb
CHANGED
@@ -17,6 +17,10 @@ module Abt
|
|
17
17
|
'abt start asana harvest' => 'Continue working, e.g. after a break',
|
18
18
|
'abt finalize asana' => 'Finalize the selected asana task'
|
19
19
|
},
|
20
|
+
'Tracking meetings (without changing the config):' => {
|
21
|
+
'abt tasks asana | grep -i standup | abt track harvest' => 'Track on asana meeting task without changing any configuration',
|
22
|
+
'abt tasks harvest | grep -i comment | abt track harvest' => 'Track on harvest "Comment"-task (will prompt for a comment)'
|
23
|
+
},
|
20
24
|
'Command output can be piped, e.g.:' => {
|
21
25
|
'abt tasks asana | grep -i <name of task>' => nil,
|
22
26
|
'abt tasks asana | grep -i <name of task> | abt start' => nil
|
data/lib/abt/git_config.rb
CHANGED
@@ -32,6 +32,19 @@ module Abt
|
|
32
32
|
set(key, value)
|
33
33
|
end
|
34
34
|
|
35
|
+
def full_keys
|
36
|
+
if scope == 'local' && !self.class.local_available?
|
37
|
+
raise StandardError, 'Local configuration is not available outside a git repository'
|
38
|
+
end
|
39
|
+
|
40
|
+
`git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
|
41
|
+
end
|
42
|
+
|
43
|
+
def keys
|
44
|
+
offset = namespace.length + 1
|
45
|
+
full_keys.map { |key| key[offset..-1] }
|
46
|
+
end
|
47
|
+
|
35
48
|
def local
|
36
49
|
@local ||= begin
|
37
50
|
if scope == 'local'
|
@@ -20,6 +20,17 @@ module Abt
|
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
+
def require_project!
|
24
|
+
cli.abort 'No current/specified project. Did you initialize Asana?' if project_gid.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def require_task!
|
28
|
+
if project_gid.nil?
|
29
|
+
cli.abort 'No current/specified project. Did you initialize Asana and pick a task?'
|
30
|
+
end
|
31
|
+
cli.abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
|
32
|
+
end
|
33
|
+
|
23
34
|
def same_args_as_config?
|
24
35
|
project_gid == config.project_gid && task_gid == config.task_gid
|
25
36
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class Add < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'add asana[:<project-gid>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Create a new task for the current/specified Asana project'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
require_project!
|
18
|
+
|
19
|
+
task
|
20
|
+
print_task(project, task)
|
21
|
+
|
22
|
+
move_task if section
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def task
|
28
|
+
@task ||= begin
|
29
|
+
body = {
|
30
|
+
data: {
|
31
|
+
name: name,
|
32
|
+
notes: notes,
|
33
|
+
projects: [project_gid]
|
34
|
+
}
|
35
|
+
}
|
36
|
+
cli.warn 'Creating task'
|
37
|
+
api.post('tasks', Oj.dump(body, mode: :json))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def move_task
|
42
|
+
body = { data: { task: task['gid'] } }
|
43
|
+
body_json = Oj.dump(body, mode: :json)
|
44
|
+
api.post("sections/#{section['gid']}/addTask", body_json)
|
45
|
+
end
|
46
|
+
|
47
|
+
def name
|
48
|
+
@name ||= cli.prompt 'Enter task description'
|
49
|
+
end
|
50
|
+
|
51
|
+
def notes
|
52
|
+
@notes ||= cli.prompt 'Enter task notes'
|
53
|
+
end
|
54
|
+
|
55
|
+
def project
|
56
|
+
@project ||= api.get("projects/#{project_gid}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def section
|
60
|
+
@section ||= cli.prompt_choice 'Add to section?', sections, ['q', 'Don\'t add to section']
|
61
|
+
end
|
62
|
+
|
63
|
+
def sections
|
64
|
+
@sections ||= begin
|
65
|
+
cli.warn 'Fetching sections...'
|
66
|
+
api.get_paged("projects/#{project_gid}/sections", opt_fields: 'name')
|
67
|
+
rescue Abt::HttpError::HttpError
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -14,6 +14,8 @@ module Abt
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def call
|
17
|
+
require_project!
|
18
|
+
|
17
19
|
if same_args_as_config? || !config.local_available?
|
18
20
|
show_current_configuration
|
19
21
|
else
|
@@ -25,9 +27,7 @@ module Abt
|
|
25
27
|
private
|
26
28
|
|
27
29
|
def show_current_configuration
|
28
|
-
if
|
29
|
-
cli.warn 'No project selected'
|
30
|
-
elsif task_gid.nil?
|
30
|
+
if task_gid.nil?
|
31
31
|
print_project(project)
|
32
32
|
else
|
33
33
|
print_task(project, task)
|
@@ -17,7 +17,7 @@ module Abt
|
|
17
17
|
unless config.local_available?
|
18
18
|
cli.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?
|
@@ -13,7 +13,8 @@ module Abt
|
|
13
13
|
'Print Harvest time entry data for Asana task as json. Used by harvest start script.'
|
14
14
|
end
|
15
15
|
|
16
|
-
def call
|
16
|
+
def call
|
17
|
+
require_task!
|
17
18
|
ensure_current_is_valid!
|
18
19
|
|
19
20
|
body = {
|
@@ -21,9 +22,7 @@ module Abt
|
|
21
22
|
external_reference: {
|
22
23
|
id: task_gid.to_i,
|
23
24
|
group_id: project_gid.to_i,
|
24
|
-
permalink: task['permalink_url']
|
25
|
-
service: 'app.asana.com',
|
26
|
-
service_icon_url: 'https://proxy.harvestfiles.com/production_harvestapp_public/uploads/platform_icons/app.asana.com.png'
|
25
|
+
permalink: task['permalink_url']
|
27
26
|
}
|
28
27
|
}
|
29
28
|
|
@@ -13,6 +13,11 @@ module Abt
|
|
13
13
|
'Pick Asana project for current git repository'
|
14
14
|
end
|
15
15
|
|
16
|
+
def initialize(cli:, **)
|
17
|
+
@config = Configuration.new(cli: cli)
|
18
|
+
@cli = cli
|
19
|
+
end
|
20
|
+
|
16
21
|
def call
|
17
22
|
cli.abort 'Must be run inside a git repository' unless config.local_available?
|
18
23
|
|
@@ -15,10 +15,11 @@ module Abt
|
|
15
15
|
|
16
16
|
def call
|
17
17
|
cli.abort 'Must be run inside a git repository' unless config.local_available?
|
18
|
+
require_project!
|
18
19
|
|
19
20
|
cli.warn project['name']
|
20
21
|
|
21
|
-
task =
|
22
|
+
task = select_task
|
22
23
|
|
23
24
|
config.project_gid = project_gid # We might have gotten the project ID as an argument
|
24
25
|
config.task_gid = task['gid']
|
@@ -32,14 +33,26 @@ module Abt
|
|
32
33
|
@project ||= api.get("projects/#{project_gid}")
|
33
34
|
end
|
34
35
|
|
35
|
-
def
|
36
|
-
|
36
|
+
def select_task
|
37
|
+
loop do
|
37
38
|
section = cli.prompt_choice 'Which section?', sections
|
38
39
|
cli.warn 'Fetching tasks...'
|
39
|
-
|
40
|
+
tasks = tasks_in_section(section)
|
41
|
+
|
42
|
+
if tasks.length.zero?
|
43
|
+
cli.warn 'Section is empty'
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
task = cli.prompt_choice 'Select a task', tasks, true
|
48
|
+
return task if task
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
52
|
+
def tasks_in_section(section)
|
53
|
+
api.get_paged('tasks', section: section['gid'], opt_fields: 'name,permalink_url')
|
54
|
+
end
|
55
|
+
|
43
56
|
def sections
|
44
57
|
@sections ||= begin
|
45
58
|
cli.warn 'Fetching sections...'
|
@@ -14,9 +14,9 @@ module Abt
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def call
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
require_project!
|
18
|
+
|
19
|
+
if task_gid.nil?
|
20
20
|
cli.print_provider_command('asana', project_gid)
|
21
21
|
else
|
22
22
|
cli.print_provider_command('asana', "#{project_gid}/#{task_gid}")
|
@@ -67,8 +67,10 @@ module Abt
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def clear_global
|
70
|
-
git.global
|
71
|
-
|
70
|
+
git.global.keys.each do |key|
|
71
|
+
cli.puts 'Deleting configuration: ' + key
|
72
|
+
git.global[key] = nil
|
73
|
+
end
|
72
74
|
end
|
73
75
|
|
74
76
|
def access_token
|
@@ -109,9 +111,13 @@ module Abt
|
|
109
111
|
workspaces = api.get_paged('workspaces')
|
110
112
|
if workspaces.empty?
|
111
113
|
cli.abort 'Your asana access token does not have access to any workspaces'
|
114
|
+
elsif workspaces.one?
|
115
|
+
workspace = workspaces.first
|
116
|
+
cli.warn "Selected Asana workspace #{workspace['name']}"
|
117
|
+
else
|
118
|
+
workspace = cli.prompt_choice('Select Asana workspace', workspaces)
|
112
119
|
end
|
113
120
|
|
114
|
-
workspace = cli.prompt_choice('Select Asana workspace', workspaces)
|
115
121
|
git.global['workspaceGid'] = workspace['gid']
|
116
122
|
workspace
|
117
123
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir.glob("#{File.expand_path(__dir__)}/devops/*.rb").sort.each { |file| require file }
|
4
|
+
Dir.glob("#{File.expand_path(__dir__)}/devops/commands/*.rb").sort.each { |file| require file }
|
5
|
+
|
6
|
+
module Abt
|
7
|
+
module Providers
|
8
|
+
module Devops
|
9
|
+
def self.command_names
|
10
|
+
Commands.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.command_class(name)
|
14
|
+
const_name = Helpers.command_to_const(name)
|
15
|
+
Commands.const_get(const_name) if Commands.const_defined?(const_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|