abt-cli 0.0.19 → 0.0.24
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/abt +3 -3
- data/lib/abt.rb +6 -6
- data/lib/abt/ari.rb +2 -2
- data/lib/abt/ari_list.rb +1 -1
- data/lib/abt/base_command.rb +7 -7
- data/lib/abt/cli.rb +49 -47
- data/lib/abt/cli/arguments_parser.rb +6 -3
- data/lib/abt/cli/global_commands.rb +23 -0
- data/lib/abt/cli/global_commands/commands.rb +23 -0
- data/lib/abt/cli/global_commands/examples.rb +23 -0
- data/lib/abt/cli/global_commands/help.rb +23 -0
- data/lib/abt/cli/global_commands/readme.rb +23 -0
- data/lib/abt/cli/global_commands/share.rb +36 -0
- data/lib/abt/cli/global_commands/version.rb +23 -0
- data/lib/abt/cli/prompt.rb +64 -52
- data/lib/abt/docs.rb +48 -26
- data/lib/abt/docs/cli.rb +3 -3
- data/lib/abt/docs/markdown.rb +10 -7
- data/lib/abt/git_config.rb +4 -6
- data/lib/abt/helpers.rb +26 -8
- data/lib/abt/providers/asana/api.rb +9 -9
- data/lib/abt/providers/asana/base_command.rb +12 -10
- data/lib/abt/providers/asana/commands/add.rb +13 -12
- data/lib/abt/providers/asana/commands/branch_name.rb +8 -8
- data/lib/abt/providers/asana/commands/clear.rb +7 -8
- data/lib/abt/providers/asana/commands/current.rb +14 -15
- data/lib/abt/providers/asana/commands/finalize.rb +17 -18
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +18 -16
- data/lib/abt/providers/asana/commands/init.rb +8 -41
- data/lib/abt/providers/asana/commands/pick.rb +22 -26
- data/lib/abt/providers/asana/commands/projects.rb +5 -5
- data/lib/abt/providers/asana/commands/share.rb +7 -5
- data/lib/abt/providers/asana/commands/start.rb +28 -21
- data/lib/abt/providers/asana/commands/tasks.rb +6 -6
- data/lib/abt/providers/asana/configuration.rb +37 -29
- data/lib/abt/providers/asana/path.rb +6 -6
- data/lib/abt/providers/devops/api.rb +12 -12
- data/lib/abt/providers/devops/base_command.rb +14 -10
- data/lib/abt/providers/devops/commands/boards.rb +5 -7
- data/lib/abt/providers/devops/commands/branch_name.rb +9 -9
- data/lib/abt/providers/devops/commands/clear.rb +7 -8
- data/lib/abt/providers/devops/commands/current.rb +17 -18
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +21 -19
- data/lib/abt/providers/devops/commands/init.rb +21 -14
- data/lib/abt/providers/devops/commands/pick.rb +25 -19
- data/lib/abt/providers/devops/commands/share.rb +7 -5
- data/lib/abt/providers/devops/commands/{work-items.rb → work_items.rb} +3 -3
- data/lib/abt/providers/devops/configuration.rb +15 -15
- data/lib/abt/providers/devops/path.rb +7 -6
- data/lib/abt/providers/git/commands/branch.rb +23 -21
- data/lib/abt/providers/harvest/api.rb +8 -8
- data/lib/abt/providers/harvest/base_command.rb +10 -8
- data/lib/abt/providers/harvest/commands/clear.rb +7 -8
- data/lib/abt/providers/harvest/commands/current.rb +13 -14
- data/lib/abt/providers/harvest/commands/init.rb +10 -39
- data/lib/abt/providers/harvest/commands/pick.rb +15 -11
- data/lib/abt/providers/harvest/commands/projects.rb +5 -5
- data/lib/abt/providers/harvest/commands/share.rb +7 -5
- data/lib/abt/providers/harvest/commands/start.rb +5 -3
- data/lib/abt/providers/harvest/commands/stop.rb +12 -12
- data/lib/abt/providers/harvest/commands/tasks.rb +7 -7
- data/lib/abt/providers/harvest/commands/track.rb +52 -37
- data/lib/abt/providers/harvest/configuration.rb +18 -18
- data/lib/abt/providers/harvest/path.rb +6 -6
- data/lib/abt/version.rb +1 -1
- metadata +12 -5
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class Cli
|
5
|
+
module GlobalCommands
|
6
|
+
class Version < Abt::BaseCommand
|
7
|
+
def self.usage
|
8
|
+
"abt version"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.description
|
12
|
+
"Print abt version"
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :cli
|
16
|
+
|
17
|
+
def perform
|
18
|
+
puts(Abt::VERSION)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/abt/cli/prompt.rb
CHANGED
@@ -10,38 +10,44 @@ module Abt
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def text(question)
|
13
|
-
output.print
|
14
|
-
read_user_input
|
13
|
+
output.print("#{question.strip}: ")
|
14
|
+
Abt::Helpers.read_user_input
|
15
15
|
end
|
16
16
|
|
17
17
|
def boolean(text)
|
18
18
|
output.puts text
|
19
19
|
|
20
20
|
loop do
|
21
|
-
output.print
|
22
|
-
|
23
|
-
case read_user_input
|
24
|
-
when
|
25
|
-
when
|
26
|
-
else
|
27
|
-
output.puts 'Invalid choice'
|
28
|
-
next
|
29
|
-
end
|
21
|
+
output.print("(y / n): ")
|
22
|
+
|
23
|
+
case Abt::Helpers.read_user_input
|
24
|
+
when "y", "Y" then return true
|
25
|
+
when "n", "N" then return false
|
26
|
+
else output.puts "Invalid choice" end
|
30
27
|
end
|
31
28
|
end
|
32
29
|
|
33
|
-
def choice(text, options, nil_option
|
34
|
-
output.puts "#{text}:"
|
30
|
+
def choice(text, options, nil_option: false)
|
31
|
+
output.puts "#{text.strip}:"
|
35
32
|
|
36
33
|
if options.length.zero?
|
37
|
-
raise Abort,
|
34
|
+
raise Abort, "No available options" unless nil_option
|
38
35
|
|
39
|
-
output.puts
|
36
|
+
output.puts "No available options"
|
40
37
|
return nil
|
41
38
|
end
|
42
39
|
|
43
40
|
print_options(options)
|
44
|
-
|
41
|
+
select_option(options, nil_option)
|
42
|
+
end
|
43
|
+
|
44
|
+
def search(text, options)
|
45
|
+
output.puts text
|
46
|
+
|
47
|
+
loop do
|
48
|
+
choice = get_search_result(options)
|
49
|
+
break choice unless choice.nil?
|
50
|
+
end
|
45
51
|
end
|
46
52
|
|
47
53
|
private
|
@@ -52,74 +58,80 @@ module Abt
|
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
55
|
-
def
|
56
|
-
|
57
|
-
number = read_option_number(options.length, nil_option)
|
58
|
-
if number.nil?
|
59
|
-
return nil if nil_option
|
60
|
-
|
61
|
-
next
|
62
|
-
end
|
61
|
+
def select_option(options, nil_option)
|
62
|
+
number = prompt_valid_option_number(options, nil_option)
|
63
63
|
|
64
|
-
|
64
|
+
return nil if number.nil?
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
option = options[number - 1]
|
67
|
+
output.puts "Selected: (#{number}) #{option['name']}"
|
68
|
+
option
|
69
69
|
end
|
70
70
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
str += nil_option_string(nil_option)
|
75
|
-
str += '): '
|
76
|
-
output.print str
|
77
|
-
|
78
|
-
input = read_user_input
|
71
|
+
def prompt_valid_option_number(options, nil_option)
|
72
|
+
output.print(options_info(options, nil_option))
|
73
|
+
input = Abt::Helpers.read_user_input
|
79
74
|
|
80
75
|
return nil if nil_option && input == nil_option_character(nil_option)
|
81
76
|
|
82
77
|
option_number = input.to_i
|
83
|
-
|
84
|
-
output.puts 'Invalid selection'
|
85
|
-
return nil
|
86
|
-
end
|
78
|
+
return option_number if (1..options.length).cover?(option_number)
|
87
79
|
|
88
|
-
|
80
|
+
output.puts "Invalid selection"
|
81
|
+
|
82
|
+
# Prompt again if the selection was invalid
|
83
|
+
prompt_valid_option_number(options, nil_option)
|
84
|
+
end
|
85
|
+
|
86
|
+
def options_info(options, nil_option)
|
87
|
+
str = "("
|
88
|
+
str += options.length > 1 ? "1-#{options.length}" : "1"
|
89
|
+
str += nil_option_string(nil_option)
|
90
|
+
str += "): "
|
91
|
+
str
|
89
92
|
end
|
90
93
|
|
91
94
|
def nil_option_string(nil_option)
|
92
|
-
return
|
95
|
+
return "" unless nil_option
|
93
96
|
|
94
97
|
", #{nil_option_character(nil_option)}: #{nil_option_description(nil_option)}"
|
95
98
|
end
|
96
99
|
|
97
100
|
def nil_option_character(nil_option)
|
98
|
-
return
|
101
|
+
return "q" if nil_option == true
|
99
102
|
|
100
103
|
nil_option[0]
|
101
104
|
end
|
102
105
|
|
103
106
|
def nil_option_description(nil_option)
|
104
|
-
return
|
107
|
+
return "back" if nil_option == true
|
105
108
|
return nil_option if nil_option.is_a?(String)
|
106
109
|
|
107
110
|
nil_option[1]
|
108
111
|
end
|
109
112
|
|
110
|
-
def
|
111
|
-
|
113
|
+
def get_search_result(options)
|
114
|
+
matches = matches_for_string(text("Enter search"), options)
|
115
|
+
if matches.empty?
|
116
|
+
output.puts("No matches")
|
117
|
+
return
|
118
|
+
end
|
119
|
+
|
120
|
+
output.puts("Showing the 10 first matches") if matches.size > 10
|
121
|
+
choice("Select a match", matches[0...10], nil_option: true)
|
112
122
|
end
|
113
123
|
|
114
|
-
def
|
115
|
-
|
116
|
-
candidates = ['/dev/tty', 'CON:'] # Unix: '/dev/tty', Windows: 'CON:'
|
117
|
-
selected = candidates.find { |candidate| File.exist?(candidate) }
|
118
|
-
raise Abort, 'Unable to prompt for user input' if selected.nil?
|
124
|
+
def matches_for_string(string, options)
|
125
|
+
search_string = sanitize_string(string)
|
119
126
|
|
120
|
-
|
127
|
+
options.select do |option|
|
128
|
+
sanitize_string(option["name"]).include?(search_string)
|
121
129
|
end
|
122
130
|
end
|
131
|
+
|
132
|
+
def sanitize_string(string)
|
133
|
+
string.downcase.gsub(/[^\w]/, "")
|
134
|
+
end
|
123
135
|
end
|
124
136
|
end
|
125
137
|
end
|
data/lib/abt/docs.rb
CHANGED
@@ -9,49 +9,71 @@ module Abt
|
|
9
9
|
class << self
|
10
10
|
def basic_examples
|
11
11
|
{
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
"Getting started:" => {
|
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
|
+
"abt stop harvest" => "Stop time tracker",
|
17
|
+
"abt start asana harvest" => "Continue working, e.g., after a break",
|
18
|
+
"abt finalize asana" => "Finalize the selected asana task"
|
19
19
|
}
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
-
def extended_examples
|
23
|
+
def extended_examples # rubocop:disable Metrics/MethodLength
|
24
24
|
{
|
25
|
-
|
26
|
-
|
27
|
-
'abt pick harvest -d | abt track harvest -c "Name of meeting"' =>
|
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"
|
28
28
|
},
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
"Many commands output ARIs that can be piped into other commands:" => {
|
30
|
+
"abt tasks asana | grep -i <name of task>" => nil,
|
31
|
+
"abt tasks asana | grep -i <name of task> | abt start" => nil
|
32
32
|
},
|
33
|
-
|
34
|
-
'abt share asana harvest | tr "\n" " "' =>
|
35
|
-
'abt share asana harvest | tr "\n" " " | pbcopy' =>
|
36
|
-
|
37
|
-
|
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
38
|
},
|
39
|
-
|
40
|
-
'abt start harvest -c "comment"' =>
|
41
|
-
'abt start harvest -c "comment" -- asana' =>
|
42
|
-
|
39
|
+
"Flags:" => {
|
40
|
+
'abt start harvest -c "comment"' => "Add command flags after ARIs",
|
41
|
+
'abt start harvest -c "comment" -- asana' =>
|
42
|
+
"Use -- to end a list of flags, so that it can be followed by another ARI",
|
43
|
+
'abt pick harvest | abt start -c "comment"' =>
|
44
|
+
"Flags placed directly after a command applies to the piped in ARI"
|
43
45
|
}
|
44
46
|
}
|
45
47
|
end
|
46
48
|
|
47
49
|
def providers
|
48
|
-
@providers ||=
|
49
|
-
|
50
|
+
@providers ||= begin
|
51
|
+
providers = {}
|
52
|
+
|
53
|
+
providers["Global"] = global_command_definitions
|
54
|
+
|
55
|
+
Abt.schemes.sort.each_with_object(providers) do |scheme, definition|
|
56
|
+
definition[scheme] = command_definitions(scheme)
|
57
|
+
end
|
58
|
+
|
59
|
+
providers
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
53
63
|
private
|
54
64
|
|
65
|
+
def global_command_definitions
|
66
|
+
global_command_names = Abt::Cli::GlobalCommands.command_names
|
67
|
+
global_command_names.each_with_object({}) do |name, definition|
|
68
|
+
command_class = Abt::Cli::GlobalCommands.command_class(name)
|
69
|
+
full_name = "abt #{name}"
|
70
|
+
|
71
|
+
if command_class.respond_to?(:usage) && command_class.respond_to?(:description)
|
72
|
+
definition[full_name] = [command_class.usage.strip, command_class.description.strip]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
55
77
|
def command_definitions(scheme)
|
56
78
|
provider = Abt.scheme_provider(scheme)
|
57
79
|
provider.command_names.each_with_object({}) do |name, definition|
|
@@ -59,7 +81,7 @@ module Abt
|
|
59
81
|
full_name = "abt #{name} #{scheme}"
|
60
82
|
|
61
83
|
if command_class.respond_to?(:usage) && command_class.respond_to?(:description)
|
62
|
-
definition[full_name] = [command_class.usage, command_class.description]
|
84
|
+
definition[full_name] = [command_class.usage.strip, command_class.description.strip]
|
63
85
|
end
|
64
86
|
end
|
65
87
|
end
|
data/lib/abt/docs/cli.rb
CHANGED
@@ -45,14 +45,14 @@ module Abt
|
|
45
45
|
private
|
46
46
|
|
47
47
|
def usage_line
|
48
|
-
|
48
|
+
"abt <command> [<ARI>] [<options> --] [<ARI>] ..."
|
49
49
|
end
|
50
50
|
|
51
51
|
def formatted_examples(example_groups)
|
52
52
|
lines = []
|
53
53
|
|
54
54
|
example_groups.each_with_index do |(title, examples), index|
|
55
|
-
lines <<
|
55
|
+
lines << "" unless index.zero?
|
56
56
|
lines << title
|
57
57
|
|
58
58
|
max_length = examples.keys.map(&:length).max
|
@@ -68,7 +68,7 @@ module Abt
|
|
68
68
|
lines = []
|
69
69
|
|
70
70
|
Docs.providers.each_with_index do |(scheme, commands_definition), index|
|
71
|
-
lines <<
|
71
|
+
lines << "" unless index.zero?
|
72
72
|
lines << "#{inflector.humanize(scheme)}:"
|
73
73
|
|
74
74
|
max_length = commands_definition.keys.map(&:length).max
|
data/lib/abt/docs/markdown.rb
CHANGED
@@ -50,13 +50,12 @@ module Abt
|
|
50
50
|
def example_commands
|
51
51
|
lines = []
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
lines << '' unless index.zero?
|
53
|
+
complete_examples.each_with_index do |(title, commands), index|
|
54
|
+
lines << "" unless index.zero?
|
56
55
|
lines << title
|
57
56
|
|
58
57
|
commands.each do |(command, description)|
|
59
|
-
formatted_description = description.nil? ?
|
58
|
+
formatted_description = description.nil? ? "" : ": #{description}"
|
60
59
|
lines << "- `#{command}`#{formatted_description}"
|
61
60
|
end
|
62
61
|
end
|
@@ -68,10 +67,10 @@ module Abt
|
|
68
67
|
lines = []
|
69
68
|
|
70
69
|
Docs.providers.each_with_index do |(scheme, commands), index|
|
71
|
-
lines <<
|
70
|
+
lines << "" unless index.zero?
|
72
71
|
lines << "### #{inflector.humanize(scheme)}"
|
73
|
-
lines <<
|
74
|
-
lines <<
|
72
|
+
lines << "| Command | Description |"
|
73
|
+
lines << "| :------ | :---------- |"
|
75
74
|
|
76
75
|
max_length = commands.values.map(&:first).map(&:length).max
|
77
76
|
|
@@ -84,6 +83,10 @@ module Abt
|
|
84
83
|
lines.join("\n")
|
85
84
|
end
|
86
85
|
|
86
|
+
def complete_examples
|
87
|
+
Docs.basic_examples.merge(Docs.extended_examples)
|
88
|
+
end
|
89
|
+
|
87
90
|
def inflector
|
88
91
|
Dry::Inflector.new
|
89
92
|
end
|
data/lib/abt/git_config.rb
CHANGED
@@ -6,12 +6,10 @@ module Abt
|
|
6
6
|
|
7
7
|
class UnsafeNamespaceError < StandardError; end
|
8
8
|
|
9
|
-
def initialize(scope =
|
9
|
+
def initialize(scope = "local", namespace = "")
|
10
10
|
@namespace = namespace
|
11
11
|
|
12
|
-
unless %w[local global].include?
|
13
|
-
raise ArgumentError, 'scope must be "local" or "global"'
|
14
|
-
end
|
12
|
+
raise ArgumentError, 'scope must be "local" or "global"' unless %w[local global].include?(scope)
|
15
13
|
|
16
14
|
@scope = scope
|
17
15
|
end
|
@@ -50,7 +48,7 @@ module Abt
|
|
50
48
|
end
|
51
49
|
|
52
50
|
def clear(output: nil)
|
53
|
-
raise UnsafeNamespaceError,
|
51
|
+
raise UnsafeNamespaceError, "Keys can only be cleared within a namespace" if namespace.empty?
|
54
52
|
|
55
53
|
keys.each do |key|
|
56
54
|
output&.puts "Clearing #{scope}: #{key_with_namespace(key)}"
|
@@ -67,7 +65,7 @@ module Abt
|
|
67
65
|
def ensure_scope_available!
|
68
66
|
return if available?
|
69
67
|
|
70
|
-
raise StandardError,
|
68
|
+
raise StandardError, "Local configuration is not available outside a git repository"
|
71
69
|
end
|
72
70
|
|
73
71
|
def key_with_namespace(key)
|
data/lib/abt/helpers.rb
CHANGED
@@ -2,15 +2,33 @@
|
|
2
2
|
|
3
3
|
module Abt
|
4
4
|
module Helpers
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
class << self
|
6
|
+
def const_to_command(string)
|
7
|
+
string = string.to_s.dup
|
8
|
+
string[0] = string[0].downcase
|
9
|
+
string.gsub(/([A-Z])/, '-\1').downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
def command_to_const(string)
|
13
|
+
inflector = Dry::Inflector.new
|
14
|
+
inflector.camelize(inflector.underscore(string))
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_user_input
|
18
|
+
open(tty_path, &:gets).strip # rubocop:disable Security/Open
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def tty_path
|
24
|
+
@tty_path ||= begin
|
25
|
+
candidates = ["/dev/tty", "CON:"] # Unix: '/dev/tty', Windows: 'CON:'
|
26
|
+
selected = candidates.find { |candidate| File.exist?(candidate) }
|
27
|
+
raise Abort, "Unable to prompt for user input" if selected.nil?
|
10
28
|
|
11
|
-
|
12
|
-
|
13
|
-
|
29
|
+
selected
|
30
|
+
end
|
31
|
+
end
|
14
32
|
end
|
15
33
|
end
|
16
34
|
end
|