abt-cli 0.0.9 → 0.0.14
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 +0 -6
- data/lib/abt.rb +8 -0
- data/lib/abt/cli.rb +5 -5
- data/lib/abt/cli/dialogs.rb +28 -9
- data/lib/abt/git_config.rb +28 -11
- data/lib/abt/helpers.rb +1 -1
- 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/branch-name.rb +44 -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 +1 -0
- 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 +4 -2
- data/lib/abt/providers/devops.rb +19 -0
- data/lib/abt/providers/devops/api.rb +95 -0
- data/lib/abt/providers/devops/base_command.rb +98 -0
- data/lib/abt/providers/devops/commands/boards.rb +34 -0
- data/lib/abt/providers/devops/commands/branch-name.rb +45 -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/git.rb +19 -0
- data/lib/abt/providers/git/commands/branch.rb +80 -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 +10 -11
- data/lib/abt/providers/harvest/commands/tasks.rb +2 -0
- data/lib/abt/providers/harvest/commands/track.rb +6 -4
- data/lib/abt/providers/harvest/configuration.rb +4 -3
- data/lib/abt/version.rb +1 -1
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6355433a65cf9e49738ab971a8886e657cefff8d3a8322e44d140a18fa7a6be
|
4
|
+
data.tar.gz: f71c9909c3c8063f24365d80db1ae91223876be98cda4e6681a5f37e017af2ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 809f27b09f6bbf41eefca395f95621b9bd7c48cb3f78cd5e7bf62c48a87895669fa74aebad4ea31fdee6ed484bf8944bb6216059b93066db2cfe118bc65c6e34
|
7
|
+
data.tar.gz: '032284e3f7e465618c2c039dbafa84855548bcee41d56725606fd2b48e58c008ba89010be8c25e78f714f3c1e16cc8a30f3e8021a0a5a531617dc0d54393ce1e'
|
data/bin/abt
CHANGED
data/lib/abt.rb
CHANGED
@@ -1,10 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'dry-inflector'
|
4
|
+
require 'faraday'
|
5
|
+
require 'oj'
|
6
|
+
require 'open3'
|
7
|
+
require 'stringio'
|
8
|
+
|
3
9
|
Dir.glob("#{File.dirname(File.absolute_path(__FILE__))}/abt/*.rb").sort.each do |file|
|
4
10
|
require file
|
5
11
|
end
|
6
12
|
|
7
13
|
module Abt
|
14
|
+
module Providers; end
|
15
|
+
|
8
16
|
def self.provider_names
|
9
17
|
Providers.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
|
10
18
|
end
|
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
|
-
abort 'No input from pipe' if
|
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,18 +24,18 @@ module Abt
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def prompt_choice(text, options,
|
27
|
+
def prompt_choice(text, options, nil_option = false)
|
28
28
|
warn "#{text}:"
|
29
29
|
|
30
30
|
if options.length.zero?
|
31
|
-
abort 'No available options' unless
|
31
|
+
abort 'No available options' unless nil_option
|
32
32
|
|
33
33
|
warn 'No available options'
|
34
34
|
return nil
|
35
35
|
end
|
36
36
|
|
37
37
|
print_options(options)
|
38
|
-
select_options(options,
|
38
|
+
select_options(options, nil_option)
|
39
39
|
end
|
40
40
|
|
41
41
|
private
|
@@ -46,11 +46,11 @@ module Abt
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def select_options(options,
|
49
|
+
def select_options(options, nil_option)
|
50
50
|
loop do
|
51
|
-
number = read_option_number(options.length,
|
51
|
+
number = read_option_number(options.length, nil_option)
|
52
52
|
if number.nil?
|
53
|
-
return nil if
|
53
|
+
return nil if nil_option
|
54
54
|
|
55
55
|
next
|
56
56
|
end
|
@@ -62,12 +62,12 @@ module Abt
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
def read_option_number(options_length,
|
66
|
-
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)}): "
|
67
67
|
|
68
68
|
input = read_user_input
|
69
69
|
|
70
|
-
return nil if
|
70
|
+
return nil if nil_option && input == nil_option_character(nil_option)
|
71
71
|
|
72
72
|
option_number = input.to_i
|
73
73
|
if option_number <= 0 || option_number > options_length
|
@@ -78,6 +78,25 @@ module Abt
|
|
78
78
|
option_number
|
79
79
|
end
|
80
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
|
+
|
81
100
|
def read_user_input
|
82
101
|
open('/dev/tty', &:gets).strip
|
83
102
|
end
|
data/lib/abt/git_config.rb
CHANGED
@@ -4,13 +4,17 @@ module Abt
|
|
4
4
|
class GitConfig
|
5
5
|
attr_reader :namespace, :scope
|
6
6
|
|
7
|
+
LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND = 'git config --local -l'
|
8
|
+
|
7
9
|
def self.local_available?
|
8
|
-
@local_available
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
return @local_available if instance_variables.include?(:@local_available)
|
11
|
+
|
12
|
+
@local_available = begin
|
13
|
+
success = false
|
14
|
+
Open3.popen3(LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND) do |_i, _o, _e, thread|
|
15
|
+
success = thread.value.success?
|
12
16
|
end
|
13
|
-
|
17
|
+
success
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
@@ -32,6 +36,17 @@ module Abt
|
|
32
36
|
set(key, value)
|
33
37
|
end
|
34
38
|
|
39
|
+
def keys
|
40
|
+
offset = namespace.length + 1
|
41
|
+
full_keys.map { |key| key[offset..-1] }
|
42
|
+
end
|
43
|
+
|
44
|
+
def full_keys
|
45
|
+
ensure_scope_available!
|
46
|
+
|
47
|
+
`git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
|
48
|
+
end
|
49
|
+
|
35
50
|
def local
|
36
51
|
@local ||= begin
|
37
52
|
if scope == 'local'
|
@@ -54,23 +69,25 @@ module Abt
|
|
54
69
|
|
55
70
|
private
|
56
71
|
|
72
|
+
def ensure_scope_available!
|
73
|
+
return if scope != 'local' || self.class.local_available?
|
74
|
+
|
75
|
+
raise StandardError, 'Local configuration is not available outside a git repository'
|
76
|
+
end
|
77
|
+
|
57
78
|
def key_with_namespace(key)
|
58
79
|
namespace.empty? ? key : "#{namespace}.#{key}"
|
59
80
|
end
|
60
81
|
|
61
82
|
def get(key)
|
62
|
-
|
63
|
-
raise StandardError, 'Local configuration is not available outside a git repository'
|
64
|
-
end
|
83
|
+
ensure_scope_available!
|
65
84
|
|
66
85
|
git_value = `git config --#{scope} --get #{key_with_namespace(key).inspect}`.strip
|
67
86
|
git_value.empty? ? nil : git_value
|
68
87
|
end
|
69
88
|
|
70
89
|
def set(key, value)
|
71
|
-
|
72
|
-
raise StandardError, 'Local configuration is not available outside a git repository'
|
73
|
-
end
|
90
|
+
ensure_scope_available!
|
74
91
|
|
75
92
|
if value.nil? || value.empty?
|
76
93
|
`git config --#{scope} --unset #{key_with_namespace(key).inspect}`
|
data/lib/abt/helpers.rb
CHANGED
@@ -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
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
module Commands
|
7
|
+
class BranchName < BaseCommand
|
8
|
+
def self.command
|
9
|
+
'branch-name asana[:<project-gid>/<task-gid>]'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
'Suggest a git branch name for the current/specified task.'
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
require_task!
|
18
|
+
ensure_current_is_valid!
|
19
|
+
|
20
|
+
cli.puts name
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def name
|
26
|
+
task['name'].downcase.gsub(/[^\w]+/, '-')
|
27
|
+
end
|
28
|
+
|
29
|
+
def ensure_current_is_valid!
|
30
|
+
cli.abort "Invalid task gid: #{task_gid}" if task.nil?
|
31
|
+
|
32
|
+
return if task['memberships'].any? { |m| m.dig('project', 'gid') == project_gid }
|
33
|
+
|
34
|
+
cli.abort "Invalid project gid: #{project_gid}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def task
|
38
|
+
@task ||= api.get("tasks/#{task_gid}", opt_fields: 'name,memberships.project')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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
|
|