abt-cli 0.0.18 → 0.0.19
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- 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 +6 -9
- data/lib/abt/cli/arguments_parser.rb +1 -23
- data/lib/abt/cli/prompt.rb +5 -4
- data/lib/abt/docs.rb +6 -5
- data/lib/abt/docs/markdown.rb +1 -1
- data/lib/abt/git_config.rb +20 -36
- data/lib/abt/providers/asana/base_command.rb +13 -33
- data/lib/abt/providers/asana/commands/add.rb +9 -7
- data/lib/abt/providers/asana/commands/branch_name.rb +9 -4
- data/lib/abt/providers/asana/commands/clear.rb +2 -0
- data/lib/abt/providers/asana/commands/current.rb +19 -34
- data/lib/abt/providers/asana/commands/finalize.rb +3 -3
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +9 -4
- data/lib/abt/providers/asana/commands/init.rb +6 -6
- data/lib/abt/providers/asana/commands/pick.rb +16 -11
- data/lib/abt/providers/asana/commands/projects.rb +1 -1
- data/lib/abt/providers/asana/commands/share.rb +2 -6
- data/lib/abt/providers/asana/commands/start.rb +14 -12
- data/lib/abt/providers/asana/commands/tasks.rb +4 -3
- data/lib/abt/providers/asana/configuration.rb +18 -24
- 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 +13 -38
- data/lib/abt/providers/devops/commands/boards.rb +2 -2
- data/lib/abt/providers/devops/commands/branch_name.rb +7 -3
- data/lib/abt/providers/devops/commands/clear.rb +2 -0
- data/lib/abt/providers/devops/commands/current.rb +14 -38
- data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +9 -1
- data/lib/abt/providers/devops/commands/init.rb +15 -15
- data/lib/abt/providers/devops/commands/pick.rb +5 -12
- data/lib/abt/providers/devops/commands/share.rb +3 -4
- data/lib/abt/providers/devops/commands/work-items.rb +1 -1
- data/lib/abt/providers/devops/configuration.rb +17 -46
- data/lib/abt/providers/devops/path.rb +50 -0
- data/lib/abt/providers/git/commands/branch.rb +14 -8
- data/lib/abt/providers/harvest/base_command.rb +14 -32
- data/lib/abt/providers/harvest/commands/clear.rb +2 -0
- data/lib/abt/providers/harvest/commands/current.rb +24 -31
- data/lib/abt/providers/harvest/commands/init.rb +5 -6
- data/lib/abt/providers/harvest/commands/pick.rb +3 -4
- data/lib/abt/providers/harvest/commands/projects.rb +1 -1
- data/lib/abt/providers/harvest/commands/share.rb +4 -8
- data/lib/abt/providers/harvest/commands/stop.rb +7 -7
- data/lib/abt/providers/harvest/commands/tasks.rb +4 -1
- data/lib/abt/providers/harvest/commands/track.rb +25 -18
- data/lib/abt/providers/harvest/configuration.rb +20 -29
- data/lib/abt/providers/harvest/path.rb +36 -0
- data/lib/abt/version.rb +1 -1
- metadata +8 -3
- data/lib/abt/cli/base_command.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 295db35510e60a5b46ea7f20caa01aaad247bb30cb60e61b912cab5f754637ac
|
4
|
+
data.tar.gz: e686eb40fc1ac88315c138eac56b96500c29c3f012b4bdb1d97f993a507f80b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f18aa2b4f33bb6b51689522bca357ddb0fbe7a2d2e968bcc6754959f01bf6d54b9f93c387e8a050d165063cad26e6b360f245d2e7fa317bc1fbad8c0fa0d3379
|
7
|
+
data.tar.gz: ab46d17a29d4e447472c8c4d9242aa2e96bd5195bad4083d0476054f312d358ad5555526b44edacdf2512713e646435200689f82e8a097c1dfa306ff2ae1c4df
|
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
@@ -17,7 +17,6 @@ module Abt
|
|
17
17
|
@output = output
|
18
18
|
@err_output = err_output
|
19
19
|
@prompt = Abt::Cli::Prompt.new(output: err_output)
|
20
|
-
|
21
20
|
@aris = ArgumentsParser.new(sanitized_piped_args + remaining_args).parse
|
22
21
|
end
|
23
22
|
|
@@ -105,26 +104,24 @@ module Abt
|
|
105
104
|
|
106
105
|
def process_aris
|
107
106
|
used_schemes = []
|
108
|
-
aris.each do |ari|
|
109
|
-
scheme = ari.scheme
|
110
|
-
path = ari.path
|
111
107
|
|
112
|
-
|
108
|
+
aris.each do |ari|
|
109
|
+
if used_schemes.include?(ari.scheme)
|
113
110
|
warn "Dropping command for already used scheme: #{ari}"
|
114
111
|
next
|
115
112
|
end
|
116
113
|
|
117
|
-
command_class = get_command_class(scheme)
|
114
|
+
command_class = get_command_class(ari.scheme)
|
118
115
|
next if command_class.nil?
|
119
116
|
|
120
117
|
print_command(command, ari) if output.isatty
|
121
118
|
begin
|
122
|
-
command_class.new(
|
119
|
+
command_class.new(ari: ari, cli: self).perform
|
123
120
|
rescue Exit => e
|
124
121
|
puts e.message
|
125
122
|
end
|
126
123
|
|
127
|
-
used_schemes << scheme
|
124
|
+
used_schemes << ari.scheme
|
128
125
|
end
|
129
126
|
|
130
127
|
return unless used_schemes.empty? && output.isatty
|
@@ -140,7 +137,7 @@ module Abt
|
|
140
137
|
end
|
141
138
|
|
142
139
|
def print_command(name, ari)
|
143
|
-
warn "===== #{name} #{ari} ====="
|
140
|
+
warn "===== #{name.upcase} #{ari} ====="
|
144
141
|
end
|
145
142
|
end
|
146
143
|
end
|
@@ -3,28 +3,6 @@
|
|
3
3
|
module Abt
|
4
4
|
class Cli
|
5
5
|
class ArgumentsParser
|
6
|
-
class Ari
|
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 Aris < Array
|
23
|
-
def to_s
|
24
|
-
map(&:to_s).join(' -- ')
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
6
|
attr_reader :arguments
|
29
7
|
|
30
8
|
def initialize(arguments)
|
@@ -32,7 +10,7 @@ module Abt
|
|
32
10
|
end
|
33
11
|
|
34
12
|
def parse
|
35
|
-
result =
|
13
|
+
result = AriList.new
|
36
14
|
rest = arguments.dup
|
37
15
|
|
38
16
|
until rest.empty?
|
data/lib/abt/cli/prompt.rb
CHANGED
@@ -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
|
|
data/lib/abt/docs.rb
CHANGED
@@ -26,14 +26,15 @@ module Abt
|
|
26
26
|
'abt pick asana -d | abt track harvest' => 'Track on asana meeting task',
|
27
27
|
'abt pick harvest -d | abt track harvest -c "Name of meeting"' => 'Track on separate harvest-task'
|
28
28
|
},
|
29
|
-
'
|
29
|
+
'Many commands output ARIs that can be piped into other commands:' => {
|
30
30
|
'abt tasks asana | grep -i <name of task>' => nil,
|
31
31
|
'abt tasks asana | grep -i <name of task> | abt start' => nil
|
32
32
|
},
|
33
|
-
'Sharing
|
34
|
-
'abt share asana harvest | tr "\n" " "' => 'Print current
|
35
|
-
'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy
|
36
|
-
'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'
|
37
38
|
},
|
38
39
|
'Flags:' => {
|
39
40
|
'abt start harvest -c "comment"' => 'Add command flags after ARIs',
|
data/lib/abt/docs/markdown.rb
CHANGED
@@ -15,7 +15,7 @@ module Abt
|
|
15
15
|
|
16
16
|
## How does abt work?
|
17
17
|
|
18
|
-
Abt is a hybrid
|
18
|
+
Abt is a hybrid of having small scripts each doing one thing:
|
19
19
|
- `start-asana --project-gid xxxx --task-gid yyyy`
|
20
20
|
- `start-harvest --project-id aaaa --task-id bbbb`
|
21
21
|
|
data/lib/abt/git_config.rb
CHANGED
@@ -6,21 +6,7 @@ module Abt
|
|
6
6
|
|
7
7
|
class UnsafeNamespaceError < StandardError; end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
def self.local_available?
|
12
|
-
return @local_available if instance_variables.include?(:@local_available)
|
13
|
-
|
14
|
-
@local_available = begin
|
15
|
-
success = false
|
16
|
-
Open3.popen3(LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND) do |_i, _o, _e, thread|
|
17
|
-
success = thread.value.success?
|
18
|
-
end
|
19
|
-
success
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def initialize(namespace: '', scope: 'local')
|
9
|
+
def initialize(scope = 'local', namespace = '')
|
24
10
|
@namespace = namespace
|
25
11
|
|
26
12
|
unless %w[local global].include? scope
|
@@ -30,6 +16,20 @@ module Abt
|
|
30
16
|
@scope = scope
|
31
17
|
end
|
32
18
|
|
19
|
+
def available?
|
20
|
+
unless instance_variables.include?(:available)
|
21
|
+
@available = begin
|
22
|
+
success = false
|
23
|
+
Open3.popen3(availability_check_call) do |_i, _o, _e, thread|
|
24
|
+
success = thread.value.success?
|
25
|
+
end
|
26
|
+
success
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@available
|
31
|
+
end
|
32
|
+
|
33
33
|
def [](key)
|
34
34
|
get(key)
|
35
35
|
end
|
@@ -49,26 +49,6 @@ module Abt
|
|
49
49
|
`git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
|
50
50
|
end
|
51
51
|
|
52
|
-
def local
|
53
|
-
@local ||= begin
|
54
|
-
if scope == 'local'
|
55
|
-
self
|
56
|
-
else
|
57
|
-
self.class.new(namespace: namespace, scope: 'local')
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def global
|
63
|
-
@global ||= begin
|
64
|
-
if scope == 'global'
|
65
|
-
self
|
66
|
-
else
|
67
|
-
self.class.new(namespace: namespace, scope: 'global')
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
52
|
def clear(output: nil)
|
73
53
|
raise UnsafeNamespaceError, 'Keys can only be cleared within a namespace' if namespace.empty?
|
74
54
|
|
@@ -80,8 +60,12 @@ module Abt
|
|
80
60
|
|
81
61
|
private
|
82
62
|
|
63
|
+
def availability_check_call
|
64
|
+
"git config --#{scope} -l"
|
65
|
+
end
|
66
|
+
|
83
67
|
def ensure_scope_available!
|
84
|
-
return if
|
68
|
+
return if available?
|
85
69
|
|
86
70
|
raise StandardError, 'Local configuration is not available outside a git repository'
|
87
71
|
end
|
@@ -3,63 +3,43 @@
|
|
3
3
|
module Abt
|
4
4
|
module Providers
|
5
5
|
module Asana
|
6
|
-
class BaseCommand < Abt::
|
7
|
-
|
6
|
+
class BaseCommand < Abt::BaseCommand
|
7
|
+
extend Forwardable
|
8
8
|
|
9
|
-
|
9
|
+
attr_reader :path, :config
|
10
|
+
|
11
|
+
def_delegators(:@path, :project_gid, :task_gid)
|
12
|
+
|
13
|
+
def initialize(ari:, cli:)
|
10
14
|
super
|
11
15
|
|
12
16
|
@config = Configuration.new(cli: cli)
|
13
17
|
|
14
|
-
|
15
|
-
use_current_path
|
16
|
-
else
|
17
|
-
use_path(path)
|
18
|
-
end
|
18
|
+
@path = ari.path ? Path.new(ari.path) : config.path
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
23
|
def require_project!
|
24
|
-
|
24
|
+
abort 'No current/specified project. Did you initialize Asana?' if project_gid.nil?
|
25
25
|
end
|
26
26
|
|
27
27
|
def require_task!
|
28
28
|
if project_gid.nil?
|
29
|
-
|
29
|
+
abort 'No current/specified project. Did you initialize Asana and pick a task?'
|
30
30
|
end
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def same_args_as_config?
|
35
|
-
project_gid == config.project_gid && task_gid == config.task_gid
|
31
|
+
abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
|
36
32
|
end
|
37
33
|
|
38
34
|
def print_project(project)
|
39
35
|
cli.print_ari('asana', project['gid'], project['name'])
|
40
|
-
|
36
|
+
warn project['permalink_url'] if project.key?('permalink_url') && cli.output.isatty
|
41
37
|
end
|
42
38
|
|
43
39
|
def print_task(project, task)
|
44
40
|
project = { 'gid' => project } if project.is_a?(String)
|
45
41
|
cli.print_ari('asana', "#{project['gid']}/#{task['gid']}", task['name'])
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def use_current_path
|
50
|
-
@project_gid = config.project_gid
|
51
|
-
@task_gid = config.task_gid
|
52
|
-
end
|
53
|
-
|
54
|
-
def use_path(path)
|
55
|
-
args = path.to_s.split('/')
|
56
|
-
@project_gid = args[0].to_s
|
57
|
-
@project_gid = nil if project_gid.empty?
|
58
|
-
|
59
|
-
return if project_gid.nil?
|
60
|
-
|
61
|
-
@task_gid = args[1].to_s
|
62
|
-
@task_gid = nil if @task_gid.empty?
|
42
|
+
warn task['permalink_url'] if task.key?('permalink_url') && cli.output.isatty
|
63
43
|
end
|
64
44
|
|
65
45
|
def api
|
@@ -17,9 +17,14 @@ module Abt
|
|
17
17
|
require_project!
|
18
18
|
|
19
19
|
task
|
20
|
-
|
20
|
+
warn 'Task created'
|
21
|
+
|
22
|
+
if section
|
23
|
+
move_task
|
24
|
+
warn "Moved to section: #{section['name']}"
|
25
|
+
end
|
21
26
|
|
22
|
-
|
27
|
+
print_task(project, task)
|
23
28
|
end
|
24
29
|
|
25
30
|
private
|
@@ -33,7 +38,6 @@ module Abt
|
|
33
38
|
projects: [project_gid]
|
34
39
|
}
|
35
40
|
}
|
36
|
-
cli.warn 'Creating task'
|
37
41
|
api.post('tasks', Oj.dump(body, mode: :json))
|
38
42
|
end
|
39
43
|
end
|
@@ -53,7 +57,7 @@ module Abt
|
|
53
57
|
end
|
54
58
|
|
55
59
|
def project
|
56
|
-
@project ||= api.get("projects/#{project_gid}")
|
60
|
+
@project ||= api.get("projects/#{project_gid}", opt_fields: 'name')
|
57
61
|
end
|
58
62
|
|
59
63
|
def section
|
@@ -62,10 +66,8 @@ module Abt
|
|
62
66
|
|
63
67
|
def sections
|
64
68
|
@sections ||= begin
|
65
|
-
|
69
|
+
warn 'Fetching sections...'
|
66
70
|
api.get_paged("projects/#{project_gid}/sections", opt_fields: 'name')
|
67
|
-
rescue Abt::HttpError::HttpError
|
68
|
-
[]
|
69
71
|
end
|
70
72
|
end
|
71
73
|
end
|