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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/bin/abt +3 -1
  3. data/lib/abt.rb +11 -0
  4. data/lib/abt/cli.rb +25 -29
  5. data/lib/abt/cli/dialogs.rb +2 -2
  6. data/lib/abt/cli/io.rb +21 -0
  7. data/lib/abt/{help.rb → docs.rb} +8 -14
  8. data/lib/abt/{help → docs}/cli.rb +3 -3
  9. data/lib/abt/{help → docs}/markdown.rb +3 -3
  10. data/lib/abt/helpers.rb +16 -0
  11. data/lib/abt/providers/asana.rb +9 -50
  12. data/lib/abt/providers/asana/api.rb +57 -0
  13. data/lib/abt/providers/asana/base_command.rb +5 -12
  14. data/lib/abt/providers/asana/commands/clear.rb +24 -0
  15. data/lib/abt/providers/asana/commands/clear_global.rb +24 -0
  16. data/lib/abt/providers/asana/commands/current.rb +71 -0
  17. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +50 -0
  18. data/lib/abt/providers/asana/commands/init.rb +63 -0
  19. data/lib/abt/providers/asana/commands/move.rb +56 -0
  20. data/lib/abt/providers/asana/commands/pick_task.rb +48 -0
  21. data/lib/abt/providers/asana/commands/projects.rb +32 -0
  22. data/lib/abt/providers/asana/commands/start.rb +60 -0
  23. data/lib/abt/providers/asana/commands/tasks.rb +37 -0
  24. data/lib/abt/providers/asana/configuration.rb +105 -0
  25. data/lib/abt/providers/harvest.rb +9 -0
  26. data/lib/abt/version.rb +1 -1
  27. metadata +18 -15
  28. data/lib/abt/asana_client.rb +0 -53
  29. data/lib/abt/providers/asana/clear.rb +0 -24
  30. data/lib/abt/providers/asana/clear_global.rb +0 -24
  31. data/lib/abt/providers/asana/current.rb +0 -69
  32. data/lib/abt/providers/asana/harvest_link_entry_data.rb +0 -48
  33. data/lib/abt/providers/asana/init.rb +0 -62
  34. data/lib/abt/providers/asana/move.rb +0 -54
  35. data/lib/abt/providers/asana/pick_task.rb +0 -46
  36. data/lib/abt/providers/asana/projects.rb +0 -30
  37. data/lib/abt/providers/asana/start.rb +0 -22
  38. data/lib/abt/providers/asana/tasks.rb +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 929ab52c8ebd307b90ddb7daa14f2f63eeeebda8ed9905490a13dcd2f67770da
4
- data.tar.gz: e2582299b29e39faa8d4baccaba42954e7140a447d30c3c56a92507ac00f4a09
3
+ metadata.gz: a08875790c31e73415b7e1317db284d1fe5b62ff9d2c3e66449c457c757bfe56
4
+ data.tar.gz: cb9857e53d577777371b669f5b253fe48ded2530976fc7c76982d6fef3e660e3
5
5
  SHA512:
6
- metadata.gz: dec30158debfe3e97c4969d8724d5f56d04725f0cab1aedf79840bb01ef81d9f17c5245cc538c559e47bfa245f36c5aeed8d02dc6ff21fcbae46b30f79e88c5b
7
- data.tar.gz: c74d6a7ae33ff5843385796cf21d764de61d0981501b5ac7e24691a7c61c85ae6f646abacd1f1b74417d29559ae022881969dd71598acf1190487c6b64c0af02
6
+ metadata.gz: e24c4f96b42d0eab847c1c5bdef2b79b3847c51db8b862c236f82d61217b295fcfc754933f4f998510b0377b0847f754cf3a6e2d4f937c73b95a8992ad7962f1
7
+ data.tar.gz: 2a41173f1fddbd52084dbc8943b6ebcb893a81a47ffd5a07e1194c4b518d042d6c2407c6babc8774c69822d3093b6d0d306ad864edc06302797002594bad3715
data/bin/abt CHANGED
@@ -8,7 +8,9 @@ require 'oj'
8
8
  require_relative '../lib/abt.rb'
9
9
 
10
10
  begin
11
- Abt::Cli.new(ARGV).perform
11
+ Abt::Cli.new.perform
12
+ rescue Abt::Cli::AbortError => e
13
+ abort e.message
12
14
  rescue Interrupt
13
15
  abort 'Aborted'
14
16
  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
@@ -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
- @args += args_from_stdin unless STDIN.isatty # Add piped arguments
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(command: @command, args: @args)
26
+ def perform
20
27
  handle_global_commands!
21
28
 
22
29
  abort('No provider arguments') if args.empty?
23
30
 
24
- process_providers(command: command, args: args)
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::Help::Cli.content)
44
+ puts(Abt::Docs::Cli.content)
38
45
  exit
39
46
  when '--help', '-h', 'help', 'commands'
40
- puts(Abt::Help::Cli.content)
47
+ puts(Abt::Docs::Cli.content)
41
48
  exit
42
49
  when 'help-md'
43
- puts(Abt::Help::Markdown.content)
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(command:, args:)
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(provider, command, arg_str)
78
- command_class = class_for_provider_and_command(provider, command)
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
- return false unless command_class
88
+ command = provider.command_class(command_name)
89
+ return false if command.nil?
81
90
 
82
- if STDOUT.isatty
83
- warn "===== #{command} #{provider}#{arg_str.nil? ? '' : ":#{arg_str}"} =====".upcase
91
+ if output.isatty
92
+ warn "===== #{command_name} #{provider_name}#{arg_str.nil? ? '' : ":#{arg_str}"} =====".upcase
84
93
  end
85
94
 
86
- command_class.new(arg_str: arg_str, cli: self).call
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
@@ -4,7 +4,7 @@ module Abt
4
4
  class Cli
5
5
  module Dialogs
6
6
  def prompt(question)
7
- STDERR.print "#{question}: "
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
- STDERR.print "(1-#{options_length}#{allow_back_option ? ', q: back' : ''}): "
47
+ print "(1-#{options_length}#{allow_back_option ? ', q: back' : ''}): "
48
48
 
49
49
  input = read_user_input
50
50
 
@@ -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
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir.glob("#{File.expand_path(__dir__)}/help/*.rb").sort.each do |file|
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 Help
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::Providers.constants.sort.each_with_object({}) do |provider_name, definition|
39
- provider_class = Abt::Providers.const_get(provider_name)
32
+ Abt.provider_names.sort.each_with_object({}) do |name, definition|
33
+ provider_module = Abt.provider_module(name)
40
34
 
41
- definition[commandize(provider_name)] = command_definitions(provider_class)
35
+ definition[name] = command_definitions(provider_module)
42
36
  end
43
37
  end
44
38
 
45
- def command_definitions(provider_class)
46
- provider_class.constants.sort.each_with_object({}) do |command_name, definition|
47
- command_class = provider_class.const_get(command_name)
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 Help
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
- Help.examples.each_with_index do |(title, examples), index|
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
- Help.providers.each_with_index do |(provider_name, commands_definition), index|
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 Help
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
- Help.examples.each_with_index do |(title, commands), index|
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
- Help.providers.each_with_index do |(provider_name, commands), index|
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 |'
@@ -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
@@ -1,59 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir.glob("#{File.expand_path(__dir__)}/asana/*.rb").sort.each do |file|
4
- require file
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
- class Asana
10
- class << self
11
- def workspace_gid
12
- @workspace_gid ||= begin
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
- def access_token
51
- Abt::GitConfig.prompt_global(
52
- 'abt.asana.accessToken',
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
- class Asana
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 remember_project_gid(project_gid)
49
- Abt::GitConfig.local('abt.asana.projectGid', project_gid)
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