abt-cli 0.0.15 → 0.0.16
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/cli.rb +70 -48
- data/lib/abt/cli/arguments_parser.rb +70 -0
- data/lib/abt/cli/base_command.rb +61 -0
- data/lib/abt/cli/prompt.rb +2 -2
- data/lib/abt/docs.rb +24 -18
- data/lib/abt/docs/cli.rb +42 -11
- data/lib/abt/docs/markdown.rb +36 -10
- data/lib/abt/git_config.rb +11 -0
- data/lib/abt/providers/asana/base_command.rb +13 -13
- data/lib/abt/providers/asana/commands/add.rb +3 -3
- data/lib/abt/providers/asana/commands/{branch-name.rb → branch_name.rb} +3 -3
- 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 +3 -3
- data/lib/abt/providers/asana/commands/pick.rb +13 -5
- 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 +13 -7
- data/lib/abt/providers/asana/commands/tasks.rb +3 -3
- data/lib/abt/providers/asana/configuration.rb +5 -13
- 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 → branch_name.rb} +3 -3
- 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 +3 -3
- data/lib/abt/providers/devops/commands/pick.rb +12 -5
- 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 +5 -13
- data/lib/abt/providers/git/commands/branch.rb +15 -21
- 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 +3 -3
- data/lib/abt/providers/harvest/commands/pick.rb +13 -5
- 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 +5 -11
- data/lib/abt/version.rb +1 -1
- metadata +6 -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: '09c4dedacc59650be8d22ae65041e78913fc4405bbb61109ef93217801bafcff'
|
4
|
+
data.tar.gz: 91f205c4db85e2686b8461c6c2b029a45e6e430c418041aa3ffae725d1ecd16b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42f9d332099fdcecdbdf538bd44119b75d643db6399d06fe578d15e5dff664af9dfec59a5ffa1f1235a1b922fa5727d52c20ed0b6a8c4fa795e1750b51f0943d
|
7
|
+
data.tar.gz: a88ffec46e7331823de6dc89fde11e442ac3c7bd577495a89dd2af20b879675de7fdafada270d1614556d193bb909892fd9622b8b3cd87f2836fab0aa94b1483
|
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/cli.rb
CHANGED
@@ -6,31 +6,31 @@ 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, :scheme_arguments, :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
20
|
|
21
|
-
@
|
21
|
+
@scheme_arguments = ArgumentsParser.new(sanitized_piped_args + remaining_args).parse
|
22
22
|
end
|
23
23
|
|
24
24
|
def perform
|
25
25
|
return if handle_global_commands!
|
26
26
|
|
27
|
-
abort('No
|
27
|
+
abort('No scheme arguments') if scheme_arguments.empty?
|
28
28
|
|
29
|
-
|
29
|
+
process_scheme_arguments
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
command = "#{
|
32
|
+
def print_scheme_argument(scheme, path, description = nil)
|
33
|
+
command = "#{scheme}:#{path}"
|
34
34
|
command += " # #{description}" unless description.nil?
|
35
35
|
output.puts command
|
36
36
|
end
|
@@ -48,77 +48,99 @@ module Abt
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def abort(message)
|
51
|
-
raise
|
51
|
+
raise Abort, message
|
52
|
+
end
|
53
|
+
|
54
|
+
def exit_with_message(message)
|
55
|
+
raise Exit, message
|
52
56
|
end
|
53
57
|
|
54
58
|
private
|
55
59
|
|
56
|
-
def handle_global_commands!
|
60
|
+
def handle_global_commands!
|
57
61
|
case command
|
58
62
|
when nil
|
59
63
|
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)
|
64
|
+
puts(Abt::Docs::Cli.help)
|
67
65
|
true
|
68
66
|
when '--version', '-v', 'version'
|
69
67
|
puts(Abt::VERSION)
|
70
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
|
71
81
|
else
|
72
82
|
false
|
73
83
|
end
|
74
84
|
end
|
75
85
|
|
76
|
-
def
|
77
|
-
|
86
|
+
def sanitized_piped_args
|
87
|
+
return [] if input.isatty
|
78
88
|
|
79
|
-
|
89
|
+
@sanitized_piped_args ||= begin
|
90
|
+
input_string = input.read.strip
|
80
91
|
|
81
|
-
|
82
|
-
lines_without_comments = input_string.lines.map do |line|
|
83
|
-
line.split(' # ').first
|
84
|
-
end
|
92
|
+
abort 'No input from pipe' if input_string.nil? || input_string.empty?
|
85
93
|
|
86
|
-
|
87
|
-
|
88
|
-
|
94
|
+
# Exclude comment part of piped input lines
|
95
|
+
lines_without_comments = input_string.lines.map do |line|
|
96
|
+
line.split(' # ').first
|
97
|
+
end
|
98
|
+
|
99
|
+
# Allow multiple scheme arguments on a single piped input line
|
100
|
+
# TODO: Force the user to pick a single scheme argument
|
101
|
+
joined_lines = lines_without_comments.join(' ').strip
|
102
|
+
joined_lines.split(/\s+/)
|
103
|
+
end
|
89
104
|
end
|
90
105
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
106
|
+
def process_scheme_arguments
|
107
|
+
used_schemes = []
|
108
|
+
scheme_arguments.each do |scheme_argument|
|
109
|
+
scheme = scheme_argument.scheme
|
110
|
+
path = scheme_argument.path
|
95
111
|
|
96
|
-
if
|
97
|
-
warn "Dropping command for already used
|
112
|
+
if used_schemes.include?(scheme)
|
113
|
+
warn "Dropping command for already used scheme: #{scheme_argument}"
|
98
114
|
next
|
99
115
|
end
|
100
116
|
|
101
|
-
|
102
|
-
|
117
|
+
command_class = get_command_class(scheme)
|
118
|
+
next if command_class.nil?
|
103
119
|
|
104
|
-
|
105
|
-
|
120
|
+
print_command(command, scheme_argument) if output.isatty
|
121
|
+
begin
|
122
|
+
command_class.new(path: path, cli: self, flags: scheme_argument.flags).perform
|
123
|
+
rescue Exit => e
|
124
|
+
puts e.message
|
125
|
+
end
|
126
|
+
|
127
|
+
used_schemes << scheme
|
128
|
+
end
|
106
129
|
|
107
|
-
|
108
|
-
provider = Abt.provider_module(provider_name)
|
109
|
-
return false if provider.nil?
|
130
|
+
return unless used_schemes.empty? && output.isatty
|
110
131
|
|
111
|
-
command
|
112
|
-
|
132
|
+
abort 'No providers found for command and scheme argument(s)'
|
133
|
+
end
|
113
134
|
|
114
|
-
|
135
|
+
def get_command_class(scheme)
|
136
|
+
provider = Abt.scheme_provider(scheme)
|
137
|
+
return nil if provider.nil?
|
115
138
|
|
116
|
-
|
117
|
-
true
|
139
|
+
provider.command_class(command)
|
118
140
|
end
|
119
141
|
|
120
|
-
def print_command(name,
|
121
|
-
warn "===== #{name} #{
|
142
|
+
def print_command(name, scheme_argument)
|
143
|
+
warn "===== #{name} #{scheme_argument} =====".upcase
|
122
144
|
end
|
123
145
|
end
|
124
146
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class Cli
|
5
|
+
class ArgumentsParser
|
6
|
+
class SchemeArgument
|
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 SchemeArguments < 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 = SchemeArguments.new
|
36
|
+
rest = arguments.dup
|
37
|
+
|
38
|
+
until rest.empty?
|
39
|
+
(scheme, path) = rest.shift.split(':')
|
40
|
+
flags = take_flags(rest)
|
41
|
+
|
42
|
+
result << SchemeArgument.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
|
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
|
@@ -114,7 +114,7 @@ module Abt
|
|
114
114
|
@tty_path ||= begin
|
115
115
|
candidates = ['/dev/tty', 'CON:'] # Unix: '/dev/tty', Windows: 'CON:'
|
116
116
|
selected = candidates.find { |candidate| File.exist?(candidate) }
|
117
|
-
raise
|
117
|
+
raise Abort, 'Unable to prompt for user input' if selected.nil?
|
118
118
|
|
119
119
|
selected
|
120
120
|
end
|
data/lib/abt/docs.rb
CHANGED
@@ -7,7 +7,7 @@ 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
13
|
'abt init asana harvest' => 'Setup asana and harvest project git repo in working dir',
|
@@ -16,10 +16,15 @@ module Abt
|
|
16
16
|
'abt stop harvest' => 'Stop time tracker',
|
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
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def extended_examples
|
24
|
+
{
|
20
25
|
'Tracking meetings (without changing the config):' => {
|
21
|
-
'abt
|
22
|
-
'abt
|
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'
|
23
28
|
},
|
24
29
|
'Command output can be piped, e.g.:' => {
|
25
30
|
'abt tasks asana | grep -i <name of task>' => nil,
|
@@ -29,30 +34,31 @@ module Abt
|
|
29
34
|
'abt share asana harvest | tr "\n" " "' => 'Print current configuration',
|
30
35
|
'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy configuration (mac only)',
|
31
36
|
'abt start <shared configuration>' => 'Start a shared configuration'
|
37
|
+
},
|
38
|
+
'Flags:' => {
|
39
|
+
'abt start harvest -c "comment"' => 'Add command flags after <scheme>:<path>',
|
40
|
+
'abt start harvest -c "comment" -- asana' => 'Use -- to mark the end of a flag list if it\'s to be followed by a <scheme-argument>',
|
41
|
+
'abt pick harvest | abt start -c "comment"' => 'Flags placed directly after a command applies to piped in <scheme-argument>'
|
32
42
|
}
|
33
43
|
}
|
34
44
|
end
|
35
45
|
|
36
46
|
def providers
|
37
|
-
|
47
|
+
@providers ||= Abt.schemes.sort.each_with_object({}) do |scheme, definition|
|
48
|
+
definition[scheme] = command_definitions(scheme)
|
49
|
+
end
|
38
50
|
end
|
39
51
|
|
40
52
|
private
|
41
53
|
|
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)
|
54
|
+
def command_definitions(scheme)
|
55
|
+
provider = Abt.scheme_provider(scheme)
|
56
|
+
provider.command_names.each_with_object({}) do |name, definition|
|
57
|
+
command_class = provider.command_class(name)
|
58
|
+
full_name = "abt #{name} #{scheme}"
|
53
59
|
|
54
|
-
if command_class.respond_to?(:
|
55
|
-
definition[
|
60
|
+
if command_class.respond_to?(:usage) && command_class.respond_to?(:description)
|
61
|
+
definition[full_name] = [command_class.usage, command_class.description]
|
56
62
|
end
|
57
63
|
end
|
58
64
|
end
|