abt-cli 0.0.19 → 0.0.24
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 +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
|