abt-cli 0.0.13 → 0.0.18
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 +6 -3
- data/lib/abt/cli.rb +91 -53
- data/lib/abt/cli/arguments_parser.rb +70 -0
- data/lib/abt/cli/base_command.rb +61 -0
- data/lib/abt/cli/{dialogs.rb → prompt.rb} +37 -18
- data/lib/abt/docs.rb +30 -24
- data/lib/abt/docs/cli.rb +42 -11
- data/lib/abt/docs/markdown.rb +38 -11
- data/lib/abt/git_config.rb +25 -14
- data/lib/abt/helpers.rb +1 -1
- data/lib/abt/providers/asana/base_command.rb +13 -13
- data/lib/abt/providers/asana/commands/add.rb +6 -6
- data/lib/abt/providers/asana/commands/branch_name.rb +44 -0
- data/lib/abt/providers/asana/commands/clear.rb +17 -6
- data/lib/abt/providers/asana/commands/current.rb +3 -3
- data/lib/abt/providers/asana/commands/finalize.rb +3 -3
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +3 -3
- data/lib/abt/providers/asana/commands/init.rb +5 -5
- data/lib/abt/providers/asana/commands/pick.rb +16 -8
- data/lib/abt/providers/asana/commands/projects.rb +3 -3
- data/lib/abt/providers/asana/commands/share.rb +5 -5
- data/lib/abt/providers/asana/commands/start.rb +14 -8
- data/lib/abt/providers/asana/commands/tasks.rb +3 -3
- data/lib/abt/providers/asana/configuration.rb +8 -16
- data/lib/abt/providers/devops/base_command.rb +14 -15
- data/lib/abt/providers/devops/commands/boards.rb +6 -4
- data/lib/abt/providers/devops/commands/branch_name.rb +45 -0
- data/lib/abt/providers/devops/commands/clear.rb +17 -6
- data/lib/abt/providers/devops/commands/current.rb +3 -3
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +3 -3
- data/lib/abt/providers/devops/commands/init.rb +5 -5
- data/lib/abt/providers/devops/commands/pick.rb +14 -7
- data/lib/abt/providers/devops/commands/share.rb +4 -4
- data/lib/abt/providers/devops/commands/work-items.rb +3 -3
- data/lib/abt/providers/devops/configuration.rb +7 -15
- data/lib/abt/providers/git.rb +19 -0
- data/lib/abt/providers/git/commands/branch.rb +74 -0
- data/lib/abt/providers/harvest/base_command.rb +13 -13
- data/lib/abt/providers/harvest/commands/clear.rb +17 -6
- data/lib/abt/providers/harvest/commands/current.rb +3 -3
- data/lib/abt/providers/harvest/commands/init.rb +5 -5
- data/lib/abt/providers/harvest/commands/pick.rb +15 -7
- data/lib/abt/providers/harvest/commands/projects.rb +3 -3
- data/lib/abt/providers/harvest/commands/share.rb +5 -5
- data/lib/abt/providers/harvest/commands/start.rb +6 -42
- data/lib/abt/providers/harvest/commands/stop.rb +3 -3
- data/lib/abt/providers/harvest/commands/tasks.rb +3 -3
- data/lib/abt/providers/harvest/commands/track.rb +49 -11
- data/lib/abt/providers/harvest/configuration.rb +7 -13
- data/lib/abt/version.rb +1 -1
- metadata +9 -7
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df400d04c979d979ab2ead353d68374b8266c0fff678103991cc6443512b703b
|
4
|
+
data.tar.gz: 0345141c38eae6904d11f40a3045fccd048d8009b70fe9714f5e0986f1779861
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1979110cbd58f0bc71b83ad7211b86e17c5b078e842d4f99ca839c88a88d627ab92506736cd83b6e414cbea2ec2a82592f6e8b3f1effdbd2b57df7decd63793e
|
7
|
+
data.tar.gz: 6af0e9bb436b304cda9d3acf6c86733050c9a7acffb691017d968e89e3f4e8e51b9fa4cdf5e3a85373ff2f684fa61e41afce0c1b9011d2e6b5551935b40530ea
|
data/bin/abt
CHANGED
data/lib/abt.rb
CHANGED
@@ -5,18 +5,21 @@ require 'faraday'
|
|
5
5
|
require 'oj'
|
6
6
|
require 'open3'
|
7
7
|
require 'stringio'
|
8
|
+
require 'optparse'
|
8
9
|
|
9
10
|
Dir.glob("#{File.dirname(File.absolute_path(__FILE__))}/abt/*.rb").sort.each do |file|
|
10
11
|
require file
|
11
12
|
end
|
12
13
|
|
13
14
|
module Abt
|
14
|
-
|
15
|
+
module Providers; end
|
16
|
+
|
17
|
+
def self.schemes
|
15
18
|
Providers.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
|
16
19
|
end
|
17
20
|
|
18
|
-
def self.
|
19
|
-
const_name = Helpers.command_to_const(
|
21
|
+
def self.scheme_provider(scheme)
|
22
|
+
const_name = Helpers.command_to_const(scheme)
|
20
23
|
Providers.const_get(const_name) if Providers.const_defined?(const_name)
|
21
24
|
end
|
22
25
|
end
|
data/lib/abt/cli.rb
CHANGED
@@ -6,103 +6,141 @@ end
|
|
6
6
|
|
7
7
|
module Abt
|
8
8
|
class Cli
|
9
|
-
class
|
9
|
+
class Abort < StandardError; end
|
10
|
+
class Exit < StandardError; end
|
10
11
|
|
11
|
-
|
12
|
-
include Io
|
13
|
-
|
14
|
-
attr_reader :command, :args, :input, :output, :err_output
|
12
|
+
attr_reader :command, :aris, :input, :output, :err_output, :prompt
|
15
13
|
|
16
14
|
def initialize(argv: ARGV, input: STDIN, output: STDOUT, err_output: STDERR)
|
17
|
-
(@command,
|
18
|
-
|
15
|
+
(@command, *remaining_args) = argv
|
19
16
|
@input = input
|
20
17
|
@output = output
|
21
18
|
@err_output = err_output
|
19
|
+
@prompt = Abt::Cli::Prompt.new(output: err_output)
|
22
20
|
|
23
|
-
@
|
21
|
+
@aris = ArgumentsParser.new(sanitized_piped_args + remaining_args).parse
|
24
22
|
end
|
25
23
|
|
26
24
|
def perform
|
27
|
-
handle_global_commands!
|
25
|
+
return if handle_global_commands!
|
28
26
|
|
29
|
-
abort('No
|
27
|
+
abort('No ARIs') if aris.empty?
|
30
28
|
|
31
|
-
|
29
|
+
process_aris
|
32
30
|
end
|
33
31
|
|
34
|
-
def
|
35
|
-
command = "#{
|
32
|
+
def print_ari(scheme, path, description = nil)
|
33
|
+
command = "#{scheme}:#{path}"
|
36
34
|
command += " # #{description}" unless description.nil?
|
37
35
|
output.puts command
|
38
36
|
end
|
39
37
|
|
38
|
+
def warn(*args)
|
39
|
+
err_output.puts(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def puts(*args)
|
43
|
+
output.puts(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def print(*args)
|
47
|
+
output.print(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def abort(message)
|
51
|
+
raise Abort, message
|
52
|
+
end
|
53
|
+
|
54
|
+
def exit_with_message(message)
|
55
|
+
raise Exit, message
|
56
|
+
end
|
57
|
+
|
40
58
|
private
|
41
59
|
|
42
|
-
def handle_global_commands!
|
60
|
+
def handle_global_commands!
|
43
61
|
case command
|
44
62
|
when nil
|
45
63
|
warn("No command specified\n\n")
|
46
|
-
puts(Abt::Docs::Cli.
|
47
|
-
|
48
|
-
when '--help', '-h', 'help', 'commands'
|
49
|
-
puts(Abt::Docs::Cli.content)
|
50
|
-
exit
|
51
|
-
when 'help-md'
|
52
|
-
puts(Abt::Docs::Markdown.content)
|
53
|
-
exit
|
64
|
+
puts(Abt::Docs::Cli.help)
|
65
|
+
true
|
54
66
|
when '--version', '-v', 'version'
|
55
67
|
puts(Abt::VERSION)
|
56
|
-
|
68
|
+
true
|
69
|
+
when '--help', '-h', 'help'
|
70
|
+
puts(Abt::Docs::Cli.help)
|
71
|
+
true
|
72
|
+
when 'commands'
|
73
|
+
puts(Abt::Docs::Cli.commands)
|
74
|
+
true
|
75
|
+
when 'examples'
|
76
|
+
puts(Abt::Docs::Cli.examples)
|
77
|
+
true
|
78
|
+
when 'readme'
|
79
|
+
puts(Abt::Docs::Markdown.readme)
|
80
|
+
true
|
81
|
+
else
|
82
|
+
false
|
57
83
|
end
|
58
84
|
end
|
59
85
|
|
60
|
-
def
|
61
|
-
|
86
|
+
def sanitized_piped_args
|
87
|
+
return [] if input.isatty
|
62
88
|
|
63
|
-
|
89
|
+
@sanitized_piped_args ||= begin
|
90
|
+
input_string = input.read.strip
|
64
91
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
92
|
+
abort 'No input from pipe' if input_string.nil? || input_string.empty?
|
93
|
+
|
94
|
+
# Exclude comment part of piped input lines
|
95
|
+
lines_without_comments = input_string.lines.map do |line|
|
96
|
+
line.split(' # ').first
|
97
|
+
end
|
69
98
|
|
70
|
-
|
71
|
-
|
72
|
-
|
99
|
+
# Allow multiple ARIs on a single piped input line
|
100
|
+
# TODO: Force the user to pick a single ARI
|
101
|
+
joined_lines = lines_without_comments.join(' ').strip
|
102
|
+
joined_lines.split(/\s+/)
|
103
|
+
end
|
73
104
|
end
|
74
105
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
106
|
+
def process_aris
|
107
|
+
used_schemes = []
|
108
|
+
aris.each do |ari|
|
109
|
+
scheme = ari.scheme
|
110
|
+
path = ari.path
|
79
111
|
|
80
|
-
if
|
81
|
-
warn "Dropping command for already used
|
112
|
+
if used_schemes.include?(scheme)
|
113
|
+
warn "Dropping command for already used scheme: #{ari}"
|
82
114
|
next
|
83
115
|
end
|
84
116
|
|
85
|
-
|
86
|
-
|
117
|
+
command_class = get_command_class(scheme)
|
118
|
+
next if command_class.nil?
|
87
119
|
|
88
|
-
|
89
|
-
|
120
|
+
print_command(command, ari) if output.isatty
|
121
|
+
begin
|
122
|
+
command_class.new(path: path, cli: self, flags: ari.flags).perform
|
123
|
+
rescue Exit => e
|
124
|
+
puts e.message
|
125
|
+
end
|
90
126
|
|
91
|
-
|
92
|
-
|
93
|
-
return false if provider.nil?
|
127
|
+
used_schemes << scheme
|
128
|
+
end
|
94
129
|
|
95
|
-
|
96
|
-
|
130
|
+
return unless used_schemes.empty? && output.isatty
|
131
|
+
|
132
|
+
abort 'No providers found for command and ARI(s)'
|
133
|
+
end
|
97
134
|
|
98
|
-
|
135
|
+
def get_command_class(scheme)
|
136
|
+
provider = Abt.scheme_provider(scheme)
|
137
|
+
return nil if provider.nil?
|
99
138
|
|
100
|
-
|
101
|
-
true
|
139
|
+
provider.command_class(command)
|
102
140
|
end
|
103
141
|
|
104
|
-
def print_command(name,
|
105
|
-
warn "===== #{name} #{
|
142
|
+
def print_command(name, ari)
|
143
|
+
warn "===== #{name} #{ari} =====".upcase
|
106
144
|
end
|
107
145
|
end
|
108
146
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class Cli
|
5
|
+
class ArgumentsParser
|
6
|
+
class Ari
|
7
|
+
attr_reader :scheme, :path, :flags
|
8
|
+
|
9
|
+
def initialize(scheme:, path:, flags:)
|
10
|
+
@scheme = scheme
|
11
|
+
@path = path
|
12
|
+
@flags = flags
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
str = scheme
|
17
|
+
str += ":#{path}" if path
|
18
|
+
|
19
|
+
[str, *flags].join(' ')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
class Aris < Array
|
23
|
+
def to_s
|
24
|
+
map(&:to_s).join(' -- ')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :arguments
|
29
|
+
|
30
|
+
def initialize(arguments)
|
31
|
+
@arguments = arguments
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse
|
35
|
+
result = Aris.new
|
36
|
+
rest = arguments.dup
|
37
|
+
|
38
|
+
until rest.empty?
|
39
|
+
(scheme, path) = rest.shift.split(':')
|
40
|
+
flags = take_flags(rest)
|
41
|
+
|
42
|
+
result << Ari.new(scheme: scheme, path: path, flags: flags)
|
43
|
+
end
|
44
|
+
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def take_flags(rest)
|
51
|
+
flags = []
|
52
|
+
|
53
|
+
if flag?(rest.first)
|
54
|
+
flags << rest.shift until rest.empty? || delimiter?(rest.first)
|
55
|
+
rest.shift if delimiter?(rest.first)
|
56
|
+
end
|
57
|
+
|
58
|
+
flags
|
59
|
+
end
|
60
|
+
|
61
|
+
def flag?(part)
|
62
|
+
part && part[0] == '-'
|
63
|
+
end
|
64
|
+
|
65
|
+
def delimiter?(part)
|
66
|
+
part == '--'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class Cli
|
5
|
+
class BaseCommand
|
6
|
+
def self.usage
|
7
|
+
raise NotImplementedError, 'Command classes must implement .command'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.description
|
11
|
+
raise NotImplementedError, 'Command classes must implement .description'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.flags
|
15
|
+
[]
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :path, :flags, :cli
|
19
|
+
|
20
|
+
def initialize(path:, flags:, cli:)
|
21
|
+
@cli = cli
|
22
|
+
@path = path
|
23
|
+
@flags = parse_flags(flags)
|
24
|
+
end
|
25
|
+
|
26
|
+
def perform
|
27
|
+
raise NotImplementedError, 'Command classes must implement #perform'
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def parse_flags(flags)
|
33
|
+
result = {}
|
34
|
+
|
35
|
+
flag_parser.parse!(flags.dup, into: result)
|
36
|
+
|
37
|
+
cli.exit_with_message(flag_parser.help) if result[:help]
|
38
|
+
|
39
|
+
result
|
40
|
+
rescue OptionParser::InvalidOption => e
|
41
|
+
cli.abort e.message
|
42
|
+
end
|
43
|
+
|
44
|
+
def flag_parser
|
45
|
+
@flag_parser ||= OptionParser.new do |opts|
|
46
|
+
opts.banner = <<~TXT
|
47
|
+
#{self.class.description}
|
48
|
+
|
49
|
+
Usage: #{self.class.usage}
|
50
|
+
TXT
|
51
|
+
|
52
|
+
opts.on('-h', '--help')
|
53
|
+
|
54
|
+
self.class.flags.each do |(*flag)|
|
55
|
+
opts.on(*flag)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -2,35 +2,41 @@
|
|
2
2
|
|
3
3
|
module Abt
|
4
4
|
class Cli
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
class Prompt
|
6
|
+
attr_reader :output
|
7
|
+
|
8
|
+
def initialize(output:)
|
9
|
+
@output = output
|
10
|
+
end
|
11
|
+
|
12
|
+
def text(question)
|
13
|
+
output.print "#{question}: "
|
14
|
+
read_user_input
|
9
15
|
end
|
10
16
|
|
11
|
-
def
|
12
|
-
|
17
|
+
def boolean(text)
|
18
|
+
output.puts text
|
13
19
|
|
14
20
|
loop do
|
15
|
-
|
21
|
+
output.print '(y / n): '
|
16
22
|
|
17
|
-
case read_user_input
|
23
|
+
case read_user_input
|
18
24
|
when 'y', 'Y' then return true
|
19
25
|
when 'n', 'N' then return false
|
20
26
|
else
|
21
|
-
|
27
|
+
output.puts 'Invalid choice'
|
22
28
|
next
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
27
|
-
def
|
28
|
-
|
33
|
+
def choice(text, options, nil_option = false)
|
34
|
+
output.puts "#{text}:"
|
29
35
|
|
30
36
|
if options.length.zero?
|
31
|
-
|
37
|
+
raise Abort, 'No available options' unless nil_option
|
32
38
|
|
33
|
-
|
39
|
+
output.puts 'No available options'
|
34
40
|
return nil
|
35
41
|
end
|
36
42
|
|
@@ -42,7 +48,7 @@ module Abt
|
|
42
48
|
|
43
49
|
def print_options(options)
|
44
50
|
options.each_with_index do |option, index|
|
45
|
-
|
51
|
+
output.puts "(#{index + 1}) #{option['name']}"
|
46
52
|
end
|
47
53
|
end
|
48
54
|
|
@@ -57,13 +63,16 @@ module Abt
|
|
57
63
|
|
58
64
|
option = options[number - 1]
|
59
65
|
|
60
|
-
|
66
|
+
output.puts "Selected: (#{number}) #{option['name']}"
|
61
67
|
return option
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
65
71
|
def read_option_number(options_length, nil_option)
|
66
|
-
|
72
|
+
output.print '('
|
73
|
+
output.print options_length > 1 ? "1-#{options_length}" : '1'
|
74
|
+
output.print nil_option_string(nil_option)
|
75
|
+
output.print '): '
|
67
76
|
|
68
77
|
input = read_user_input
|
69
78
|
|
@@ -71,7 +80,7 @@ module Abt
|
|
71
80
|
|
72
81
|
option_number = input.to_i
|
73
82
|
if option_number <= 0 || option_number > options_length
|
74
|
-
|
83
|
+
output.puts 'Invalid selection'
|
75
84
|
return nil
|
76
85
|
end
|
77
86
|
|
@@ -98,7 +107,17 @@ module Abt
|
|
98
107
|
end
|
99
108
|
|
100
109
|
def read_user_input
|
101
|
-
open(
|
110
|
+
open(tty_path, &:gets).strip # rubocop:disable Security/Open
|
111
|
+
end
|
112
|
+
|
113
|
+
def tty_path
|
114
|
+
@tty_path ||= begin
|
115
|
+
candidates = ['/dev/tty', 'CON:'] # Unix: '/dev/tty', Windows: 'CON:'
|
116
|
+
selected = candidates.find { |candidate| File.exist?(candidate) }
|
117
|
+
raise Abort, 'Unable to prompt for user input' if selected.nil?
|
118
|
+
|
119
|
+
selected
|
120
|
+
end
|
102
121
|
end
|
103
122
|
end
|
104
123
|
end
|