abt-cli 0.0.15 → 0.0.20
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 +68 -49
- data/lib/abt/cli/arguments_parser.rb +48 -0
- data/lib/abt/cli/prompt.rb +7 -6
- 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 +12 -10
- 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 +8 -12
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +12 -7
- data/lib/abt/providers/asana/commands/init.rb +9 -9
- data/lib/abt/providers/asana/commands/pick.rb +28 -15
- 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 +26 -18
- 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 +18 -18
- data/lib/abt/providers/devops/commands/pick.rb +16 -16
- 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 +8 -9
- data/lib/abt/providers/harvest/commands/pick.rb +15 -8
- 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 +11 -7
- 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: 6348f086170cb21625ec22595423cebbca76b0455f6dc2ad552ef072019b3929
|
4
|
+
data.tar.gz: e53c442f505ba9141e62d2b4c7883ec010c966b4d1deef0d71651b94ea11db93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 977ff0fbc908433afdf996cdd354bd33ea9a315cfebcbc456f42df731309f0502801516995f0ded85a58f8efcfeb0fe9830dd31c51efcb05f4acb58ddb3e3833
|
7
|
+
data.tar.gz: 45d25bbae2c077af24c304132703ca8ebc7a0968c04aafc598ca48a66cd3f303fe5cc108e193e24bd8f5cbb09e37d831eeba3518c360bafb1112f4ae443138cd
|
data/bin/abt
CHANGED
data/lib/abt.rb
CHANGED
@@ -5,6 +5,7 @@ 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
|
@@ -13,12 +14,12 @@ end
|
|
13
14
|
module Abt
|
14
15
|
module Providers; end
|
15
16
|
|
16
|
-
def self.
|
17
|
+
def self.schemes
|
17
18
|
Providers.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
|
18
19
|
end
|
19
20
|
|
20
|
-
def self.
|
21
|
-
const_name = Helpers.command_to_const(
|
21
|
+
def self.scheme_provider(scheme)
|
22
|
+
const_name = Helpers.command_to_const(scheme)
|
22
23
|
Providers.const_get(const_name) if Providers.const_defined?(const_name)
|
23
24
|
end
|
24
25
|
end
|
data/lib/abt/ari.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class Ari
|
5
|
+
attr_reader :scheme, :path, :flags
|
6
|
+
|
7
|
+
def initialize(scheme:, path: nil, flags: [])
|
8
|
+
@scheme = scheme
|
9
|
+
@path = path
|
10
|
+
@flags = flags
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
str = scheme
|
15
|
+
str += ":#{path}" if path
|
16
|
+
|
17
|
+
[str, *flags].join(' ')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/abt/ari_list.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class BaseCommand
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def self.usage
|
8
|
+
raise NotImplementedError, 'Command classes must implement .usage'
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.description
|
12
|
+
raise NotImplementedError, 'Command classes must implement .description'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.flags
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :ari, :cli, :flags
|
20
|
+
|
21
|
+
def_delegators(:@cli, :warn, :puts, :print, :abort, :exit_with_message)
|
22
|
+
|
23
|
+
def initialize(ari:, cli:)
|
24
|
+
@cli = cli
|
25
|
+
@ari = ari
|
26
|
+
@flags = parse_flags(ari.flags)
|
27
|
+
end
|
28
|
+
|
29
|
+
def perform
|
30
|
+
raise NotImplementedError, 'Command classes must implement #perform'
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parse_flags(flags)
|
36
|
+
result = {}
|
37
|
+
|
38
|
+
flag_parser.parse!(flags.dup, into: result)
|
39
|
+
|
40
|
+
exit_with_message(flag_parser.help) if result[:help]
|
41
|
+
|
42
|
+
result
|
43
|
+
rescue OptionParser::InvalidOption => e
|
44
|
+
abort e.message
|
45
|
+
end
|
46
|
+
|
47
|
+
def flag_parser
|
48
|
+
@flag_parser ||= OptionParser.new do |opts|
|
49
|
+
opts.banner = <<~TXT
|
50
|
+
#{self.class.description}
|
51
|
+
|
52
|
+
Usage: #{self.class.usage}
|
53
|
+
TXT
|
54
|
+
|
55
|
+
opts.on('-h', '--help')
|
56
|
+
|
57
|
+
self.class.flags.each do |(*flag)|
|
58
|
+
opts.on(*flag)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/abt/cli.rb
CHANGED
@@ -6,31 +6,30 @@ 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
|
-
attr_reader :command, :
|
12
|
+
attr_reader :command, :aris, :input, :output, :err_output, :prompt
|
12
13
|
|
13
14
|
def initialize(argv: ARGV, input: STDIN, output: STDOUT, err_output: STDERR)
|
14
|
-
(@command,
|
15
|
-
|
15
|
+
(@command, *remaining_args) = argv
|
16
16
|
@input = input
|
17
17
|
@output = output
|
18
18
|
@err_output = err_output
|
19
19
|
@prompt = Abt::Cli::Prompt.new(output: err_output)
|
20
|
-
|
21
|
-
@args += args_from_input unless input.isatty # Add piped arguments
|
20
|
+
@aris = ArgumentsParser.new(sanitized_piped_args + remaining_args).parse
|
22
21
|
end
|
23
22
|
|
24
23
|
def perform
|
25
24
|
return if handle_global_commands!
|
26
25
|
|
27
|
-
abort('No
|
26
|
+
abort('No ARIs') if aris.empty?
|
28
27
|
|
29
|
-
|
28
|
+
process_aris
|
30
29
|
end
|
31
30
|
|
32
|
-
def
|
33
|
-
command = "#{
|
31
|
+
def print_ari(scheme, path, description = nil)
|
32
|
+
command = "#{scheme}:#{path}"
|
34
33
|
command += " # #{description}" unless description.nil?
|
35
34
|
output.puts command
|
36
35
|
end
|
@@ -48,77 +47,97 @@ module Abt
|
|
48
47
|
end
|
49
48
|
|
50
49
|
def abort(message)
|
51
|
-
raise
|
50
|
+
raise Abort, message
|
51
|
+
end
|
52
|
+
|
53
|
+
def exit_with_message(message)
|
54
|
+
raise Exit, message
|
52
55
|
end
|
53
56
|
|
54
57
|
private
|
55
58
|
|
56
|
-
def handle_global_commands!
|
59
|
+
def handle_global_commands!
|
57
60
|
case command
|
58
61
|
when nil
|
59
62
|
warn("No command specified\n\n")
|
60
|
-
puts(Abt::Docs::Cli.
|
61
|
-
true
|
62
|
-
when '--help', '-h', 'help', 'commands'
|
63
|
-
puts(Abt::Docs::Cli.content)
|
64
|
-
true
|
65
|
-
when 'help-md'
|
66
|
-
puts(Abt::Docs::Markdown.content)
|
63
|
+
puts(Abt::Docs::Cli.help)
|
67
64
|
true
|
68
65
|
when '--version', '-v', 'version'
|
69
66
|
puts(Abt::VERSION)
|
70
67
|
true
|
68
|
+
when '--help', '-h', 'help'
|
69
|
+
puts(Abt::Docs::Cli.help)
|
70
|
+
true
|
71
|
+
when 'commands'
|
72
|
+
puts(Abt::Docs::Cli.commands)
|
73
|
+
true
|
74
|
+
when 'examples'
|
75
|
+
puts(Abt::Docs::Cli.examples)
|
76
|
+
true
|
77
|
+
when 'readme'
|
78
|
+
puts(Abt::Docs::Markdown.readme)
|
79
|
+
true
|
71
80
|
else
|
72
81
|
false
|
73
82
|
end
|
74
83
|
end
|
75
84
|
|
76
|
-
def
|
77
|
-
|
85
|
+
def sanitized_piped_args
|
86
|
+
return [] if input.isatty
|
78
87
|
|
79
|
-
|
88
|
+
@sanitized_piped_args ||= begin
|
89
|
+
input_string = input.read.strip
|
80
90
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
91
|
+
abort 'No input from pipe' if input_string.nil? || input_string.empty?
|
92
|
+
|
93
|
+
# Exclude comment part of piped input lines
|
94
|
+
lines_without_comments = input_string.lines.map do |line|
|
95
|
+
line.split(' # ').first
|
96
|
+
end
|
85
97
|
|
86
|
-
|
87
|
-
|
88
|
-
|
98
|
+
# Allow multiple ARIs on a single piped input line
|
99
|
+
# TODO: Force the user to pick a single ARI
|
100
|
+
joined_lines = lines_without_comments.join(' ').strip
|
101
|
+
joined_lines.split(/\s+/)
|
102
|
+
end
|
89
103
|
end
|
90
104
|
|
91
|
-
def
|
92
|
-
|
93
|
-
args.each do |provider_args|
|
94
|
-
(provider, arg_str) = provider_args.split(':')
|
105
|
+
def process_aris
|
106
|
+
used_schemes = []
|
95
107
|
|
96
|
-
|
97
|
-
|
108
|
+
aris.each do |ari|
|
109
|
+
if used_schemes.include?(ari.scheme)
|
110
|
+
warn "Dropping command for already used scheme: #{ari}"
|
98
111
|
next
|
99
112
|
end
|
100
113
|
|
101
|
-
|
102
|
-
|
114
|
+
command_class = get_command_class(ari.scheme)
|
115
|
+
next if command_class.nil?
|
103
116
|
|
104
|
-
|
105
|
-
|
117
|
+
print_command(command, ari) if output.isatty
|
118
|
+
begin
|
119
|
+
command_class.new(ari: ari, cli: self).perform
|
120
|
+
rescue Exit => e
|
121
|
+
puts e.message
|
122
|
+
end
|
106
123
|
|
107
|
-
|
108
|
-
|
109
|
-
return false if provider.nil?
|
124
|
+
used_schemes << ari.scheme
|
125
|
+
end
|
110
126
|
|
111
|
-
|
112
|
-
|
127
|
+
return unless used_schemes.empty? && output.isatty
|
128
|
+
|
129
|
+
abort 'No providers found for command and ARI(s)'
|
130
|
+
end
|
113
131
|
|
114
|
-
|
132
|
+
def get_command_class(scheme)
|
133
|
+
provider = Abt.scheme_provider(scheme)
|
134
|
+
return nil if provider.nil?
|
115
135
|
|
116
|
-
|
117
|
-
true
|
136
|
+
provider.command_class(command)
|
118
137
|
end
|
119
138
|
|
120
|
-
def print_command(name,
|
121
|
-
warn "===== #{name} #{
|
139
|
+
def print_command(name, ari)
|
140
|
+
warn "===== #{name.upcase} #{ari} ====="
|
122
141
|
end
|
123
142
|
end
|
124
143
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class Cli
|
5
|
+
class ArgumentsParser
|
6
|
+
attr_reader :arguments
|
7
|
+
|
8
|
+
def initialize(arguments)
|
9
|
+
@arguments = arguments
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse
|
13
|
+
result = AriList.new
|
14
|
+
rest = arguments.dup
|
15
|
+
|
16
|
+
until rest.empty?
|
17
|
+
(scheme, path) = rest.shift.split(':')
|
18
|
+
flags = take_flags(rest)
|
19
|
+
|
20
|
+
result << Ari.new(scheme: scheme, path: path, flags: flags)
|
21
|
+
end
|
22
|
+
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def take_flags(rest)
|
29
|
+
flags = []
|
30
|
+
|
31
|
+
if flag?(rest.first)
|
32
|
+
flags << rest.shift until rest.empty? || delimiter?(rest.first)
|
33
|
+
rest.shift if delimiter?(rest.first)
|
34
|
+
end
|
35
|
+
|
36
|
+
flags
|
37
|
+
end
|
38
|
+
|
39
|
+
def flag?(part)
|
40
|
+
part && part[0] == '-'
|
41
|
+
end
|
42
|
+
|
43
|
+
def delimiter?(part)
|
44
|
+
part == '--'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/abt/cli/prompt.rb
CHANGED
@@ -34,7 +34,7 @@ module Abt
|
|
34
34
|
output.puts "#{text}:"
|
35
35
|
|
36
36
|
if options.length.zero?
|
37
|
-
raise
|
37
|
+
raise Abort, 'No available options' unless nil_option
|
38
38
|
|
39
39
|
output.puts 'No available options'
|
40
40
|
return nil
|
@@ -69,10 +69,11 @@ module Abt
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def read_option_number(options_length, nil_option)
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
str = '('
|
73
|
+
str += options_length > 1 ? "1-#{options_length}" : '1'
|
74
|
+
str += nil_option_string(nil_option)
|
75
|
+
str += '): '
|
76
|
+
output.print str
|
76
77
|
|
77
78
|
input = read_user_input
|
78
79
|
|
@@ -114,7 +115,7 @@ module Abt
|
|
114
115
|
@tty_path ||= begin
|
115
116
|
candidates = ['/dev/tty', 'CON:'] # Unix: '/dev/tty', Windows: 'CON:'
|
116
117
|
selected = candidates.find { |candidate| File.exist?(candidate) }
|
117
|
-
raise
|
118
|
+
raise Abort, 'Unable to prompt for user input' if selected.nil?
|
118
119
|
|
119
120
|
selected
|
120
121
|
end
|
data/lib/abt/docs.rb
CHANGED
@@ -7,52 +7,59 @@ end
|
|
7
7
|
module Abt
|
8
8
|
module Docs
|
9
9
|
class << self
|
10
|
-
def
|
10
|
+
def basic_examples
|
11
11
|
{
|
12
12
|
'Getting started:' => {
|
13
|
-
'abt init asana harvest' => 'Setup asana and harvest project git repo
|
14
|
-
'abt pick harvest' => 'Pick harvest
|
15
|
-
'abt pick asana | abt start harvest' => 'Pick asana task and start
|
13
|
+
'abt init asana harvest' => 'Setup asana and harvest project for local git repo',
|
14
|
+
'abt pick harvest' => 'Pick harvest task. This will likely stay the same throughout the project',
|
15
|
+
'abt pick asana | abt start harvest' => 'Pick asana task and start tracking time',
|
16
16
|
'abt stop harvest' => 'Stop time tracker',
|
17
|
-
'abt start asana harvest' => 'Continue working, e.g
|
17
|
+
'abt start asana harvest' => 'Continue working, e.g., after a break',
|
18
18
|
'abt finalize asana' => 'Finalize the selected asana task'
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def extended_examples
|
24
|
+
{
|
25
|
+
'Tracking meetings (without switching current task setting):' => {
|
26
|
+
'abt pick asana -d | abt track harvest' => 'Track on asana meeting task',
|
27
|
+
'abt pick harvest -d | abt track harvest -c "Name of meeting"' => 'Track on separate harvest-task'
|
19
28
|
},
|
20
|
-
'
|
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
|
-
},
|
24
|
-
'Command output can be piped, e.g.:' => {
|
29
|
+
'Many commands output ARIs that can be piped into other commands:' => {
|
25
30
|
'abt tasks asana | grep -i <name of task>' => nil,
|
26
31
|
'abt tasks asana | grep -i <name of task> | abt start' => nil
|
27
32
|
},
|
28
|
-
'Sharing
|
29
|
-
'abt share asana harvest | tr "\n" " "' => 'Print current
|
30
|
-
'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy
|
31
|
-
'abt start <
|
33
|
+
'Sharing ARIs:' => {
|
34
|
+
'abt share asana harvest | tr "\n" " "' => 'Print current asana and harvest ARIs on a single line',
|
35
|
+
'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy ARIs to clipboard (mac only)',
|
36
|
+
'abt start <ARIs from coworker>' => 'Work on a task your coworker shared with you',
|
37
|
+
'abt current <ARIs from coworker> | abt start' => 'Set task as current, then start it'
|
38
|
+
},
|
39
|
+
'Flags:' => {
|
40
|
+
'abt start harvest -c "comment"' => 'Add command flags after ARIs',
|
41
|
+
'abt start harvest -c "comment" -- asana' => 'Use -- to end a list of flags, so that it can be followed by another ARI',
|
42
|
+
'abt pick harvest | abt start -c "comment"' => 'Flags placed directly after a command applies to the piped in ARI'
|
32
43
|
}
|
33
44
|
}
|
34
45
|
end
|
35
46
|
|
36
47
|
def providers
|
37
|
-
|
48
|
+
@providers ||= Abt.schemes.sort.each_with_object({}) do |scheme, definition|
|
49
|
+
definition[scheme] = command_definitions(scheme)
|
50
|
+
end
|
38
51
|
end
|
39
52
|
|
40
53
|
private
|
41
54
|
|
42
|
-
def
|
43
|
-
Abt.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def command_definitions(provider_module)
|
51
|
-
provider_module.command_names.each_with_object({}) do |name, definition|
|
52
|
-
command_class = provider_module.command_class(name)
|
55
|
+
def command_definitions(scheme)
|
56
|
+
provider = Abt.scheme_provider(scheme)
|
57
|
+
provider.command_names.each_with_object({}) do |name, definition|
|
58
|
+
command_class = provider.command_class(name)
|
59
|
+
full_name = "abt #{name} #{scheme}"
|
53
60
|
|
54
|
-
if command_class.respond_to?(:
|
55
|
-
definition[
|
61
|
+
if command_class.respond_to?(:usage) && command_class.respond_to?(:description)
|
62
|
+
definition[full_name] = [command_class.usage, command_class.description]
|
56
63
|
end
|
57
64
|
end
|
58
65
|
end
|