abt-cli 0.0.2 → 0.0.7
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 +4 -1
- data/lib/abt.rb +11 -0
- data/lib/abt/cli.rb +37 -32
- data/lib/abt/cli/dialogs.rb +18 -2
- data/lib/abt/cli/io.rb +23 -0
- data/lib/abt/docs.rb +57 -0
- data/lib/abt/{help → docs}/cli.rb +4 -4
- data/lib/abt/{help → docs}/markdown.rb +4 -4
- data/lib/abt/git_config.rb +55 -49
- data/lib/abt/helpers.rb +16 -0
- data/lib/abt/providers/asana.rb +9 -50
- data/lib/abt/providers/asana/api.rb +57 -0
- data/lib/abt/providers/asana/base_command.rb +14 -16
- data/lib/abt/providers/asana/commands/clear.rb +24 -0
- data/lib/abt/providers/asana/commands/clear_global.rb +24 -0
- data/lib/abt/providers/asana/commands/current.rb +77 -0
- data/lib/abt/providers/asana/commands/finalize.rb +71 -0
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +50 -0
- data/lib/abt/providers/asana/commands/init.rb +70 -0
- data/lib/abt/providers/asana/commands/pick.rb +55 -0
- data/lib/abt/providers/asana/commands/projects.rb +39 -0
- data/lib/abt/providers/asana/commands/share.rb +29 -0
- data/lib/abt/providers/asana/commands/start.rb +105 -0
- data/lib/abt/providers/asana/commands/tasks.rb +40 -0
- data/lib/abt/providers/asana/configuration.rb +125 -0
- data/lib/abt/providers/harvest.rb +9 -42
- data/lib/abt/providers/harvest/api.rb +62 -0
- data/lib/abt/providers/harvest/base_command.rb +12 -16
- data/lib/abt/providers/harvest/commands/clear.rb +24 -0
- data/lib/abt/providers/harvest/commands/clear_global.rb +24 -0
- data/lib/abt/providers/harvest/commands/current.rb +83 -0
- data/lib/abt/providers/harvest/commands/init.rb +83 -0
- data/lib/abt/providers/harvest/commands/pick.rb +51 -0
- data/lib/abt/providers/harvest/commands/projects.rb +40 -0
- data/lib/abt/providers/harvest/commands/share.rb +29 -0
- data/lib/abt/providers/harvest/commands/start.rb +101 -0
- data/lib/abt/providers/harvest/commands/stop.rb +58 -0
- data/lib/abt/providers/harvest/commands/tasks.rb +45 -0
- data/lib/abt/providers/harvest/configuration.rb +91 -0
- data/lib/abt/version.rb +1 -1
- metadata +32 -26
- data/lib/abt/asana_client.rb +0 -53
- data/lib/abt/harvest_client.rb +0 -58
- data/lib/abt/help.rb +0 -56
- data/lib/abt/providers/asana/clear.rb +0 -24
- data/lib/abt/providers/asana/clear_global.rb +0 -24
- data/lib/abt/providers/asana/current.rb +0 -69
- data/lib/abt/providers/asana/harvest_link_entry_data.rb +0 -48
- data/lib/abt/providers/asana/init.rb +0 -62
- data/lib/abt/providers/asana/move.rb +0 -54
- data/lib/abt/providers/asana/pick_task.rb +0 -46
- data/lib/abt/providers/asana/projects.rb +0 -30
- data/lib/abt/providers/asana/start.rb +0 -22
- data/lib/abt/providers/asana/tasks.rb +0 -35
- data/lib/abt/providers/harvest/clear.rb +0 -24
- data/lib/abt/providers/harvest/clear_global.rb +0 -24
- data/lib/abt/providers/harvest/current.rb +0 -79
- data/lib/abt/providers/harvest/init.rb +0 -61
- data/lib/abt/providers/harvest/pick_task.rb +0 -45
- data/lib/abt/providers/harvest/projects.rb +0 -29
- data/lib/abt/providers/harvest/start.rb +0 -58
- data/lib/abt/providers/harvest/stop.rb +0 -51
- data/lib/abt/providers/harvest/tasks.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd99c9123cfbd4222176495fd039015ad56ddaf0841350347169fa6809f8d6b2
|
4
|
+
data.tar.gz: 33d0adf3e73be8f5b2a6f52d61cea46285e0e2ab036b2d1b0e1b4fe1f2c0a27a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ea6b3ea3f1f2056b9e7911144542f0d2f3b568544fcd68901299fb64c84414290d7cecfc884405bde0125b096bc7033b99d3c2eb87e0598f434de79cfa76ae3
|
7
|
+
data.tar.gz: 3c8ffb1c3ba6553d16611c0f55a1c1bae3cf97d156fc79242f66b2d0b2ac622b08ab4ef049b98299553c708dc36365fc4e6158e31b556d830d863019134f34eb
|
data/bin/abt
CHANGED
@@ -4,11 +4,14 @@
|
|
4
4
|
require 'dry-inflector'
|
5
5
|
require 'faraday'
|
6
6
|
require 'oj'
|
7
|
+
require 'open3'
|
7
8
|
|
8
9
|
require_relative '../lib/abt.rb'
|
9
10
|
|
10
11
|
begin
|
11
|
-
Abt::Cli.new
|
12
|
+
Abt::Cli.new.perform
|
13
|
+
rescue Abt::Cli::AbortError => e
|
14
|
+
abort e.message
|
12
15
|
rescue Interrupt
|
13
16
|
abort 'Aborted'
|
14
17
|
end
|
data/lib/abt.rb
CHANGED
@@ -3,3 +3,14 @@
|
|
3
3
|
Dir.glob("#{File.dirname(File.absolute_path(__FILE__))}/abt/*.rb").sort.each do |file|
|
4
4
|
require file
|
5
5
|
end
|
6
|
+
|
7
|
+
module Abt
|
8
|
+
def self.provider_names
|
9
|
+
Providers.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.provider_module(name)
|
13
|
+
const_name = Helpers.command_to_const(name)
|
14
|
+
Providers.const_get(const_name) if Providers.const_defined?(const_name)
|
15
|
+
end
|
16
|
+
end
|
data/lib/abt/cli.rb
CHANGED
@@ -6,26 +6,35 @@ end
|
|
6
6
|
|
7
7
|
module Abt
|
8
8
|
class Cli
|
9
|
+
class AbortError < StandardError; end
|
10
|
+
|
9
11
|
include Dialogs
|
12
|
+
include Io
|
10
13
|
|
11
|
-
attr_reader :command, :args
|
14
|
+
attr_reader :command, :args, :input, :output, :err_output
|
12
15
|
|
13
|
-
def initialize(argv)
|
16
|
+
def initialize(argv: ARGV, input: STDIN, output: STDOUT, err_output: STDERR)
|
14
17
|
(@command, *@args) = argv
|
15
18
|
|
16
|
-
@
|
19
|
+
@input = input
|
20
|
+
@output = output
|
21
|
+
@err_output = err_output
|
22
|
+
|
23
|
+
@args += args_from_stdin unless input.isatty # Add piped arguments
|
17
24
|
end
|
18
25
|
|
19
|
-
def perform
|
26
|
+
def perform
|
20
27
|
handle_global_commands!
|
21
28
|
|
22
29
|
abort('No provider arguments') if args.empty?
|
23
30
|
|
24
|
-
process_providers
|
31
|
+
process_providers
|
25
32
|
end
|
26
33
|
|
27
|
-
def print_provider_command(provider, arg_str, description)
|
28
|
-
|
34
|
+
def print_provider_command(provider, arg_str, description = nil)
|
35
|
+
command = "#{provider}:#{arg_str}"
|
36
|
+
command += " # #{description}" unless description.nil?
|
37
|
+
output.puts command
|
29
38
|
end
|
30
39
|
|
31
40
|
private
|
@@ -34,13 +43,13 @@ module Abt
|
|
34
43
|
case command
|
35
44
|
when nil
|
36
45
|
warn("No command specified\n\n")
|
37
|
-
puts(Abt::
|
46
|
+
puts(Abt::Docs::Cli.content)
|
38
47
|
exit
|
39
48
|
when '--help', '-h', 'help', 'commands'
|
40
|
-
puts(Abt::
|
49
|
+
puts(Abt::Docs::Cli.content)
|
41
50
|
exit
|
42
51
|
when 'help-md'
|
43
|
-
puts(Abt::
|
52
|
+
puts(Abt::Docs::Markdown.content)
|
44
53
|
exit
|
45
54
|
when '--version', '-v', 'version'
|
46
55
|
puts(Abt::VERSION)
|
@@ -53,12 +62,17 @@ module Abt
|
|
53
62
|
|
54
63
|
return [] if input.nil?
|
55
64
|
|
56
|
-
input
|
57
|
-
|
65
|
+
# Exclude comment part of piped input lines
|
66
|
+
lines_without_comments = input.lines.map do |line|
|
67
|
+
line.split(' # ').first
|
58
68
|
end
|
69
|
+
|
70
|
+
# Allow multiple provider arguments on a single piped input line
|
71
|
+
joined_lines = lines_without_comments.join(' ').strip
|
72
|
+
joined_lines.split(/\s+/)
|
59
73
|
end
|
60
74
|
|
61
|
-
def process_providers
|
75
|
+
def process_providers
|
62
76
|
used_providers = []
|
63
77
|
args.each do |provider_args|
|
64
78
|
(provider, arg_str) = provider_args.split(':')
|
@@ -71,33 +85,24 @@ module Abt
|
|
71
85
|
used_providers << provider if process_provider_command(provider, command, arg_str)
|
72
86
|
end
|
73
87
|
|
74
|
-
warn 'No matching providers found for command' if used_providers.empty?
|
88
|
+
warn 'No matching providers found for command' if used_providers.empty? && output.isatty
|
75
89
|
end
|
76
90
|
|
77
|
-
def process_provider_command(
|
78
|
-
|
91
|
+
def process_provider_command(provider_name, command_name, arg_str)
|
92
|
+
provider = Abt.provider_module(provider_name)
|
93
|
+
return false if provider.nil?
|
79
94
|
|
80
|
-
|
95
|
+
command = provider.command_class(command_name)
|
96
|
+
return false if command.nil?
|
81
97
|
|
82
|
-
if
|
83
|
-
warn "===== #{command} #{provider}#{arg_str.nil? ? '' : ":#{arg_str}"} =====".upcase
|
84
|
-
end
|
98
|
+
print_command(command_name, provider_name, arg_str) if output.isatty
|
85
99
|
|
86
|
-
|
100
|
+
command.new(arg_str: arg_str, cli: self).call
|
87
101
|
true
|
88
102
|
end
|
89
103
|
|
90
|
-
def
|
91
|
-
|
92
|
-
provider_class_name = inflector.camelize(inflector.underscore(provider))
|
93
|
-
|
94
|
-
return unless Abt::Providers.const_defined? provider_class_name
|
95
|
-
|
96
|
-
provider_class = Abt::Providers.const_get provider_class_name
|
97
|
-
command_class_name = inflector.camelize(inflector.underscore(command))
|
98
|
-
return unless provider_class.const_defined? command_class_name
|
99
|
-
|
100
|
-
provider_class.const_get command_class_name
|
104
|
+
def print_command(name, provider, arg_str)
|
105
|
+
warn "===== #{name} #{provider}#{arg_str.nil? ? '' : ":#{arg_str}"} =====".upcase
|
101
106
|
end
|
102
107
|
end
|
103
108
|
end
|
data/lib/abt/cli/dialogs.rb
CHANGED
@@ -4,10 +4,26 @@ module Abt
|
|
4
4
|
class Cli
|
5
5
|
module Dialogs
|
6
6
|
def prompt(question)
|
7
|
-
|
7
|
+
err_output.print "#{question}: "
|
8
8
|
read_user_input.strip
|
9
9
|
end
|
10
10
|
|
11
|
+
def prompt_boolean(text)
|
12
|
+
warn text
|
13
|
+
|
14
|
+
loop do
|
15
|
+
err_output.print '(y / n): '
|
16
|
+
|
17
|
+
case read_user_input.strip
|
18
|
+
when 'y', 'Y' then return true
|
19
|
+
when 'n', 'N' then return false
|
20
|
+
else
|
21
|
+
warn 'Invalid choice'
|
22
|
+
next
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
11
27
|
def prompt_choice(text, options, allow_back_option = false)
|
12
28
|
if options.one?
|
13
29
|
warn "Selected: #{options.first['name']}"
|
@@ -44,7 +60,7 @@ module Abt
|
|
44
60
|
end
|
45
61
|
|
46
62
|
def read_option_number(options_length, allow_back_option)
|
47
|
-
|
63
|
+
err_output.print "(1-#{options_length}#{allow_back_option ? ', q: back' : ''}): "
|
48
64
|
|
49
65
|
input = read_user_input
|
50
66
|
|
data/lib/abt/cli/io.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class Cli
|
5
|
+
module Io
|
6
|
+
def warn(*args)
|
7
|
+
err_output.puts(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def puts(*args)
|
11
|
+
output.puts(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def print(*args)
|
15
|
+
output.print(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def abort(message)
|
19
|
+
raise AbortError, message
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/abt/docs.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir.glob("#{File.expand_path(__dir__)}/docs/*.rb").sort.each do |file|
|
4
|
+
require file
|
5
|
+
end
|
6
|
+
|
7
|
+
module Abt
|
8
|
+
module Docs
|
9
|
+
class << self
|
10
|
+
def examples # rubocop:disable Metrics/MethodLength
|
11
|
+
{
|
12
|
+
'Getting started:' => {
|
13
|
+
'abt init asana harvest' => 'Setup asana and harvest project git repo in working dir',
|
14
|
+
'abt pick harvest' => 'Pick harvest tasks, for most projects this will stay the same',
|
15
|
+
'abt pick asana | abt start harvest' => 'Pick asana task and start working',
|
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
|
+
},
|
20
|
+
'Command output can be piped, e.g.:' => {
|
21
|
+
'abt tasks asana | grep -i <name of task>' => nil,
|
22
|
+
'abt tasks asana | grep -i <name of task> | abt start' => nil
|
23
|
+
},
|
24
|
+
'Sharing configuration:' => {
|
25
|
+
'abt share asana harvest | tr "\n" " "' => 'Print current configuration',
|
26
|
+
'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy configuration (mac only)',
|
27
|
+
'abt start <shared configuration>' => 'Start a shared configuration'
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def providers
|
33
|
+
provider_definitions
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def provider_definitions
|
39
|
+
Abt.provider_names.sort.each_with_object({}) do |name, definition|
|
40
|
+
provider_module = Abt.provider_module(name)
|
41
|
+
|
42
|
+
definition[name] = command_definitions(provider_module)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def command_definitions(provider_module)
|
47
|
+
provider_module.command_names.each_with_object({}) do |name, definition|
|
48
|
+
command_class = provider_module.command_class(name)
|
49
|
+
|
50
|
+
if command_class.respond_to?(:command) && command_class.respond_to?(:description)
|
51
|
+
definition[command_class.command] = command_class.description
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Abt
|
4
|
-
module
|
4
|
+
module Docs
|
5
5
|
module Cli
|
6
6
|
class << self
|
7
7
|
def content
|
8
8
|
<<~TXT
|
9
|
-
Usage: abt <command> [<provider
|
9
|
+
Usage: abt <command> [<provider[:<arguments>]>...]
|
10
10
|
|
11
11
|
#{example_commands}
|
12
12
|
|
@@ -20,7 +20,7 @@ module Abt
|
|
20
20
|
def example_commands
|
21
21
|
lines = []
|
22
22
|
|
23
|
-
|
23
|
+
Docs.examples.each_with_index do |(title, examples), index|
|
24
24
|
lines << '' unless index.zero?
|
25
25
|
lines << title
|
26
26
|
|
@@ -36,7 +36,7 @@ module Abt
|
|
36
36
|
def providers_commands
|
37
37
|
lines = []
|
38
38
|
|
39
|
-
|
39
|
+
Docs.providers.each_with_index do |(provider_name, commands_definition), index|
|
40
40
|
lines << '' unless index.zero?
|
41
41
|
lines << "#{inflector.humanize(provider_name)}:"
|
42
42
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Abt
|
4
|
-
module
|
4
|
+
module Docs
|
5
5
|
module Markdown
|
6
6
|
class << self
|
7
7
|
def content
|
@@ -10,7 +10,7 @@ module Abt
|
|
10
10
|
This readme was generated with `abt help-md > README.md`
|
11
11
|
|
12
12
|
## Usage
|
13
|
-
`abt <command> [<provider
|
13
|
+
`abt <command> [<provider[:<arguments>]>...]`
|
14
14
|
|
15
15
|
#{example_commands}
|
16
16
|
|
@@ -24,7 +24,7 @@ module Abt
|
|
24
24
|
def example_commands
|
25
25
|
lines = []
|
26
26
|
|
27
|
-
|
27
|
+
Docs.examples.each_with_index do |(title, commands), index|
|
28
28
|
lines << '' unless index.zero?
|
29
29
|
lines << title
|
30
30
|
|
@@ -40,7 +40,7 @@ module Abt
|
|
40
40
|
def provider_commands # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
41
41
|
lines = []
|
42
42
|
|
43
|
-
|
43
|
+
Docs.providers.each_with_index do |(provider_name, commands), index|
|
44
44
|
lines << '' unless index.zero?
|
45
45
|
lines << "### #{inflector.humanize(provider_name)}"
|
46
46
|
lines << '| Command | Description |'
|
data/lib/abt/git_config.rb
CHANGED
@@ -2,76 +2,82 @@
|
|
2
2
|
|
3
3
|
module Abt
|
4
4
|
class GitConfig
|
5
|
-
|
6
|
-
def local(*args)
|
7
|
-
git_config(true, *args)
|
8
|
-
end
|
9
|
-
|
10
|
-
def global(*args)
|
11
|
-
git_config(false, *args)
|
12
|
-
end
|
5
|
+
attr_reader :namespace, :scope
|
13
6
|
|
14
|
-
|
15
|
-
|
7
|
+
def self.local_available?
|
8
|
+
@local_available ||= begin
|
9
|
+
status = nil
|
10
|
+
Open3.popen3('git config --local -l') do |_i, _o, _e, thread|
|
11
|
+
status = thread.value
|
12
|
+
end
|
13
|
+
status.success?
|
16
14
|
end
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
end
|
17
|
+
def initialize(namespace: '', scope: 'local')
|
18
|
+
@namespace = namespace
|
21
19
|
|
22
|
-
|
23
|
-
|
20
|
+
unless %w[local global].include? scope
|
21
|
+
raise ArgumentError, 'scope must be "local" or "global"'
|
24
22
|
end
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
24
|
+
@scope = scope
|
25
|
+
end
|
29
26
|
|
30
|
-
|
27
|
+
def [](key)
|
28
|
+
get(key)
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def []=(key, value)
|
32
|
+
set(key, value)
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
def local
|
36
|
+
@local ||= begin
|
37
|
+
if scope == 'local'
|
38
|
+
self
|
40
39
|
else
|
41
|
-
|
42
|
-
git_value.empty? ? nil : git_value
|
40
|
+
self.class.new(namespace: namespace, scope: 'local')
|
43
41
|
end
|
44
42
|
end
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
def global
|
46
|
+
@global ||= begin
|
47
|
+
if scope == 'global'
|
48
|
+
self
|
49
|
+
else
|
50
|
+
self.class.new(namespace: namespace, scope: 'global')
|
51
|
+
end
|
51
52
|
end
|
53
|
+
end
|
52
54
|
|
53
|
-
|
54
|
-
value = git_config(local, key)
|
55
|
+
private
|
55
56
|
|
56
|
-
|
57
|
+
def key_with_namespace(key)
|
58
|
+
namespace.empty? ? key : "#{namespace}.#{key}"
|
59
|
+
end
|
57
60
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
TXT
|
61
|
+
def get(key)
|
62
|
+
if scope == 'local' && !self.class.local_available?
|
63
|
+
raise StandardError, 'Local configuration is not available outside a git repository'
|
64
|
+
end
|
63
65
|
|
64
|
-
|
66
|
+
git_value = `git config --#{scope} --get #{key_with_namespace(key).inspect}`.strip
|
67
|
+
git_value.empty? ? nil : git_value
|
68
|
+
end
|
65
69
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
git_config(local, key, new_value)
|
70
|
-
end
|
70
|
+
def set(key, value)
|
71
|
+
if scope == 'local' && !self.class.local_available?
|
72
|
+
raise StandardError, 'Local configuration is not available outside a git repository'
|
71
73
|
end
|
72
74
|
|
73
|
-
|
74
|
-
|
75
|
+
if value.nil? || value.empty?
|
76
|
+
`git config --#{scope} --unset #{key_with_namespace(key).inspect}`
|
77
|
+
nil
|
78
|
+
else
|
79
|
+
`git config --#{scope} --replace-all #{key_with_namespace(key).inspect} #{value.inspect}`
|
80
|
+
value
|
75
81
|
end
|
76
82
|
end
|
77
83
|
end
|