abt-cli 0.0.2 → 0.0.3
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 -1
- data/lib/abt.rb +11 -0
- data/lib/abt/cli.rb +25 -29
- data/lib/abt/cli/dialogs.rb +2 -2
- data/lib/abt/cli/io.rb +21 -0
- data/lib/abt/{help.rb → docs.rb} +8 -14
- data/lib/abt/{help → docs}/cli.rb +3 -3
- data/lib/abt/{help → docs}/markdown.rb +3 -3
- 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 +5 -12
- 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 +71 -0
- data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +50 -0
- data/lib/abt/providers/asana/commands/init.rb +63 -0
- data/lib/abt/providers/asana/commands/move.rb +56 -0
- data/lib/abt/providers/asana/commands/pick_task.rb +48 -0
- data/lib/abt/providers/asana/commands/projects.rb +32 -0
- data/lib/abt/providers/asana/commands/start.rb +60 -0
- data/lib/abt/providers/asana/commands/tasks.rb +37 -0
- data/lib/abt/providers/asana/configuration.rb +105 -0
- data/lib/abt/providers/harvest.rb +9 -0
- data/lib/abt/version.rb +1 -1
- metadata +18 -15
- data/lib/abt/asana_client.rb +0 -53
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a08875790c31e73415b7e1317db284d1fe5b62ff9d2c3e66449c457c757bfe56
|
4
|
+
data.tar.gz: cb9857e53d577777371b669f5b253fe48ded2530976fc7c76982d6fef3e660e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e24c4f96b42d0eab847c1c5bdef2b79b3847c51db8b862c236f82d61217b295fcfc754933f4f998510b0377b0847f754cf3a6e2d4f937c73b95a8992ad7962f1
|
7
|
+
data.tar.gz: 2a41173f1fddbd52084dbc8943b6ebcb893a81a47ffd5a07e1194c4b518d042d6c2407c6babc8774c69822d3093b6d0d306ad864edc06302797002594bad3715
|
data/bin/abt
CHANGED
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,22 +6,29 @@ 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
34
|
def print_provider_command(provider, arg_str, description)
|
@@ -34,13 +41,13 @@ module Abt
|
|
34
41
|
case command
|
35
42
|
when nil
|
36
43
|
warn("No command specified\n\n")
|
37
|
-
puts(Abt::
|
44
|
+
puts(Abt::Docs::Cli.content)
|
38
45
|
exit
|
39
46
|
when '--help', '-h', 'help', 'commands'
|
40
|
-
puts(Abt::
|
47
|
+
puts(Abt::Docs::Cli.content)
|
41
48
|
exit
|
42
49
|
when 'help-md'
|
43
|
-
puts(Abt::
|
50
|
+
puts(Abt::Docs::Markdown.content)
|
44
51
|
exit
|
45
52
|
when '--version', '-v', 'version'
|
46
53
|
puts(Abt::VERSION)
|
@@ -58,7 +65,7 @@ module Abt
|
|
58
65
|
end
|
59
66
|
end
|
60
67
|
|
61
|
-
def process_providers
|
68
|
+
def process_providers
|
62
69
|
used_providers = []
|
63
70
|
args.each do |provider_args|
|
64
71
|
(provider, arg_str) = provider_args.split(':')
|
@@ -71,33 +78,22 @@ module Abt
|
|
71
78
|
used_providers << provider if process_provider_command(provider, command, arg_str)
|
72
79
|
end
|
73
80
|
|
74
|
-
warn 'No matching providers found for command' if used_providers.empty?
|
81
|
+
warn 'No matching providers found for command' if used_providers.empty? && output.isatty
|
75
82
|
end
|
76
83
|
|
77
|
-
def process_provider_command(
|
78
|
-
|
84
|
+
def process_provider_command(provider_name, command_name, arg_str)
|
85
|
+
provider = Abt.provider_module(provider_name)
|
86
|
+
return false if provider.nil?
|
79
87
|
|
80
|
-
|
88
|
+
command = provider.command_class(command_name)
|
89
|
+
return false if command.nil?
|
81
90
|
|
82
|
-
if
|
83
|
-
warn "===== #{
|
91
|
+
if output.isatty
|
92
|
+
warn "===== #{command_name} #{provider_name}#{arg_str.nil? ? '' : ":#{arg_str}"} =====".upcase
|
84
93
|
end
|
85
94
|
|
86
|
-
|
95
|
+
command.new(arg_str: arg_str, cli: self).call
|
87
96
|
true
|
88
97
|
end
|
89
|
-
|
90
|
-
def class_for_provider_and_command(provider, command)
|
91
|
-
inflector = Dry::Inflector.new
|
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
|
101
|
-
end
|
102
98
|
end
|
103
99
|
end
|
data/lib/abt/cli/dialogs.rb
CHANGED
@@ -4,7 +4,7 @@ module Abt
|
|
4
4
|
class Cli
|
5
5
|
module Dialogs
|
6
6
|
def prompt(question)
|
7
|
-
|
7
|
+
print "#{question}: "
|
8
8
|
read_user_input.strip
|
9
9
|
end
|
10
10
|
|
@@ -44,7 +44,7 @@ module Abt
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def read_option_number(options_length, allow_back_option)
|
47
|
-
|
47
|
+
print "(1-#{options_length}#{allow_back_option ? ', q: back' : ''}): "
|
48
48
|
|
49
49
|
input = read_user_input
|
50
50
|
|
data/lib/abt/cli/io.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
class Cli
|
5
|
+
module Io
|
6
|
+
%i[puts print].each do |method_name|
|
7
|
+
define_method(method_name) do |*args|
|
8
|
+
output.puts(*args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def warn(*args)
|
13
|
+
err_output.puts(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def abort(message)
|
17
|
+
raise AbortError, message
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/abt/{help.rb → docs.rb}
RENAMED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
Dir.glob("#{File.expand_path(__dir__)}/
|
3
|
+
Dir.glob("#{File.expand_path(__dir__)}/docs/*.rb").sort.each do |file|
|
4
4
|
require file
|
5
5
|
end
|
6
6
|
|
7
7
|
module Abt
|
8
|
-
module
|
8
|
+
module Docs
|
9
9
|
class << self
|
10
10
|
def examples # rubocop:disable Metrics/MethodLength
|
11
11
|
{
|
@@ -28,23 +28,17 @@ module Abt
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
-
def commandize(string)
|
32
|
-
string = string.to_s
|
33
|
-
string[0] = string[0].downcase
|
34
|
-
string.gsub(/([A-Z])/, '-\1').downcase
|
35
|
-
end
|
36
|
-
|
37
31
|
def provider_definitions
|
38
|
-
Abt
|
39
|
-
|
32
|
+
Abt.provider_names.sort.each_with_object({}) do |name, definition|
|
33
|
+
provider_module = Abt.provider_module(name)
|
40
34
|
|
41
|
-
definition[
|
35
|
+
definition[name] = command_definitions(provider_module)
|
42
36
|
end
|
43
37
|
end
|
44
38
|
|
45
|
-
def command_definitions(
|
46
|
-
|
47
|
-
command_class =
|
39
|
+
def command_definitions(provider_module)
|
40
|
+
provider_module.command_names.each_with_object({}) do |name, definition|
|
41
|
+
command_class = provider_module.command_class(name)
|
48
42
|
|
49
43
|
if command_class.respond_to?(:command) && command_class.respond_to?(:description)
|
50
44
|
definition[command_class.command] = command_class.description
|
@@ -1,7 +1,7 @@
|
|
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
|
@@ -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
|
@@ -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/helpers.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Helpers
|
5
|
+
def self.const_to_command(string)
|
6
|
+
string = string.to_s
|
7
|
+
string[0] = string[0].downcase
|
8
|
+
string.gsub(/([A-Z])/, '-\1').downcase
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.command_to_const(string)
|
12
|
+
inflector = Dry::Inflector.new
|
13
|
+
inflector.camelize(inflector.underscore(string))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/abt/providers/asana.rb
CHANGED
@@ -1,59 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
Dir.glob("#{File.expand_path(__dir__)}/asana/*.rb").sort.each
|
4
|
-
|
5
|
-
end
|
3
|
+
Dir.glob("#{File.expand_path(__dir__)}/asana/*.rb").sort.each { |file| require file }
|
4
|
+
Dir.glob("#{File.expand_path(__dir__)}/asana/commands/*.rb").sort.each { |file| require file }
|
6
5
|
|
7
6
|
module Abt
|
8
7
|
module Providers
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
current = Abt::GitConfig.global('abt.asana.workspaceGid')
|
14
|
-
if current.nil?
|
15
|
-
prompt_workspace['gid']
|
16
|
-
else
|
17
|
-
current
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def clear
|
23
|
-
Abt::GitConfig.unset_local('abt.asana.projectGid')
|
24
|
-
Abt::GitConfig.unset_local('abt.asana.taskGid')
|
25
|
-
end
|
26
|
-
|
27
|
-
def clear_global
|
28
|
-
Abt::GitConfig.unset_global('abt.asana.workspaceGid')
|
29
|
-
Abt::GitConfig.unset_global('abt.asana.accessToken')
|
30
|
-
end
|
31
|
-
|
32
|
-
def client
|
33
|
-
Abt::AsanaClient.new(access_token: access_token)
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def prompt_workspace
|
39
|
-
workspaces = client.get_paged('workspaces')
|
40
|
-
if workspaces.empty?
|
41
|
-
abort 'Your asana access token does not have access to any workspaces'
|
42
|
-
end
|
43
|
-
|
44
|
-
# TODO: Handle if there are multiple workspaces
|
45
|
-
workspace = workspaces.first
|
46
|
-
Abt::GitConfig.global('abt.asana.workspaceGid', workspace['gid'])
|
47
|
-
workspace
|
48
|
-
end
|
8
|
+
module Asana
|
9
|
+
def self.command_names
|
10
|
+
Commands.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
|
11
|
+
end
|
49
12
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
'Please enter your personal asana access_token',
|
54
|
-
'Create a personal access token here: https://app.asana.com/0/developer-console'
|
55
|
-
)
|
56
|
-
end
|
13
|
+
def self.command_class(name)
|
14
|
+
const_name = Helpers.command_to_const(name)
|
15
|
+
Commands.const_get(const_name) if Commands.const_defined?(const_name)
|
57
16
|
end
|
58
17
|
end
|
59
18
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abt
|
4
|
+
module Providers
|
5
|
+
module Asana
|
6
|
+
class Api
|
7
|
+
API_ENDPOINT = 'https://app.asana.com/api/1.0'
|
8
|
+
VERBS = %i[get post patch].freeze
|
9
|
+
|
10
|
+
attr_reader :access_token
|
11
|
+
|
12
|
+
def initialize(access_token:)
|
13
|
+
@access_token = access_token
|
14
|
+
end
|
15
|
+
|
16
|
+
VERBS.each do |verb|
|
17
|
+
define_method(verb) do |*args|
|
18
|
+
request(verb, *args)['data']
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_paged(path, query = {})
|
23
|
+
records = []
|
24
|
+
|
25
|
+
loop do
|
26
|
+
result = request(:get, path, query.merge(limit: 100))
|
27
|
+
records += result['data']
|
28
|
+
break if result['next_page'].nil?
|
29
|
+
|
30
|
+
path = result['next_page']['path'][1..-1]
|
31
|
+
end
|
32
|
+
|
33
|
+
records
|
34
|
+
end
|
35
|
+
|
36
|
+
def request(*args)
|
37
|
+
response = connection.public_send(*args)
|
38
|
+
|
39
|
+
if response.success?
|
40
|
+
Oj.load(response.body)
|
41
|
+
else
|
42
|
+
error_class = Abt::HttpError.error_class_for_status(response.status)
|
43
|
+
encoded_response_body = response.body.force_encoding('utf-8')
|
44
|
+
raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def connection
|
49
|
+
@connection ||= Faraday.new(API_ENDPOINT) do |connection|
|
50
|
+
connection.headers['Authorization'] = "Bearer #{access_token}"
|
51
|
+
connection.headers['Content-Type'] = 'application/json'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -2,12 +2,13 @@
|
|
2
2
|
|
3
3
|
module Abt
|
4
4
|
module Providers
|
5
|
-
|
5
|
+
module Asana
|
6
6
|
class BaseCommand
|
7
|
-
attr_reader :arg_str, :project_gid, :task_gid, :cli
|
7
|
+
attr_reader :arg_str, :project_gid, :task_gid, :cli, :config
|
8
8
|
|
9
9
|
def initialize(arg_str:, cli:)
|
10
10
|
@arg_str = arg_str
|
11
|
+
@config = Configuration.new(cli: cli)
|
11
12
|
|
12
13
|
if arg_str.nil?
|
13
14
|
use_current_args
|
@@ -45,16 +46,8 @@ module Abt
|
|
45
46
|
@task_gid = nil if @task_gid.empty?
|
46
47
|
end
|
47
48
|
|
48
|
-
def
|
49
|
-
Abt::
|
50
|
-
end
|
51
|
-
|
52
|
-
def remember_task_gid(task_gid)
|
53
|
-
if task_gid.nil?
|
54
|
-
Abt::GitConfig.unset_local('abt.asana.taskGid')
|
55
|
-
else
|
56
|
-
Abt::GitConfig.local('abt.asana.taskGid', task_gid)
|
57
|
-
end
|
49
|
+
def api
|
50
|
+
Abt::Providers::Asana::Api.new(access_token: config.access_token)
|
58
51
|
end
|
59
52
|
end
|
60
53
|
end
|