abt-cli 0.0.2 → 0.0.3
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 -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
|