abt-cli 0.0.15 → 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- 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
|