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.
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
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Asana
6
+ class Configuration
7
+ attr_accessor :cli
8
+
9
+ def initialize(cli:)
10
+ @cli = cli
11
+ end
12
+
13
+ def project_gid
14
+ Abt::GitConfig.local('abt.asana.projectGid')
15
+ end
16
+
17
+ def task_gid
18
+ Abt::GitConfig.local('abt.asana.taskGid')
19
+ end
20
+
21
+ def workspace_gid
22
+ @workspace_gid ||= begin
23
+ current = Abt::GitConfig.global('abt.asana.workspaceGid')
24
+ if current.nil?
25
+ prompt_workspace['gid']
26
+ else
27
+ current
28
+ end
29
+ end
30
+ end
31
+
32
+ def wip_section_gid
33
+ @wip_section_gid ||= begin
34
+ current = Abt::GitConfig.global('abt.asana.wipSectionGid')
35
+ if current.nil?
36
+ prompt_wip_section['gid']
37
+ else
38
+ current
39
+ end
40
+ end
41
+ end
42
+
43
+ def project_gid=(value)
44
+ return if project_gid == value
45
+
46
+ clear_local
47
+ Abt::GitConfig.local('abt.asana.projectGid', value) unless value.nil?
48
+ end
49
+
50
+ def task_gid=(value)
51
+ if value.nil?
52
+ Abt::GitConfig.unset_local('abt.asana.taskGid')
53
+ elsif task_gid != value
54
+ Abt::GitConfig.local('abt.asana.taskGid', value)
55
+ end
56
+ end
57
+
58
+ def clear_local
59
+ Abt::GitConfig.unset_local('abt.asana.projectGid')
60
+ Abt::GitConfig.unset_local('abt.asana.taskGid')
61
+ Abt::GitConfig.unset_local('abt.asana.wipSectionGid')
62
+ end
63
+
64
+ def clear_global
65
+ Abt::GitConfig.unset_global('abt.asana.workspaceGid')
66
+ Abt::GitConfig.unset_global('abt.asana.accessToken')
67
+ end
68
+
69
+ def access_token
70
+ Abt::GitConfig.prompt_global(
71
+ 'abt.asana.accessToken',
72
+ 'Please enter your personal asana access_token',
73
+ 'Create a personal access token here: https://app.asana.com/0/developer-console'
74
+ )
75
+ end
76
+
77
+ private
78
+
79
+ def prompt_wip_section
80
+ sections = api.get_paged("projects/#{project_gid}/sections")
81
+
82
+ section = cli.prompt_choice('Select WIP (Work In Progress) section', sections)
83
+ Abt::GitConfig.global('abt.asana.wipSectionGid', section['gid'])
84
+ section
85
+ end
86
+
87
+ def prompt_workspace
88
+ workspaces = api.get_paged('workspaces')
89
+ if workspaces.empty?
90
+ cli.abort 'Your asana access token does not have access to any workspaces'
91
+ end
92
+
93
+ # TODO: Handle if there are multiple workspaces
94
+ workspace = workspaces.first
95
+ Abt::GitConfig.global('abt.asana.workspaceGid', workspace['gid'])
96
+ workspace
97
+ end
98
+
99
+ def api
100
+ Abt::Providers::Asana::Api.new(access_token: access_token)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -8,6 +8,15 @@ module Abt
8
8
  module Providers
9
9
  class Harvest
10
10
  class << self
11
+ def command_names
12
+ constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
13
+ end
14
+
15
+ def command_class(name)
16
+ const_name = Helpers.command_to_const(name)
17
+ const_get(const_name) if const_defined?(const_name)
18
+ end
19
+
11
20
  def user_id
12
21
  Abt::GitConfig.prompt_global(
13
22
  'abt.harvest.userId',
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Abt
4
- VERSION = '0.0.2'
4
+ VERSION = '0.0.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abt-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesper Sørensen
@@ -75,28 +75,31 @@ extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
77
  - "./lib/abt.rb"
78
- - "./lib/abt/asana_client.rb"
79
78
  - "./lib/abt/cli.rb"
80
79
  - "./lib/abt/cli/dialogs.rb"
80
+ - "./lib/abt/cli/io.rb"
81
+ - "./lib/abt/docs.rb"
82
+ - "./lib/abt/docs/cli.rb"
83
+ - "./lib/abt/docs/markdown.rb"
81
84
  - "./lib/abt/git_config.rb"
82
85
  - "./lib/abt/harvest_client.rb"
83
- - "./lib/abt/help.rb"
84
- - "./lib/abt/help/cli.rb"
85
- - "./lib/abt/help/markdown.rb"
86
+ - "./lib/abt/helpers.rb"
86
87
  - "./lib/abt/http_error.rb"
87
88
  - "./lib/abt/providers.rb"
88
89
  - "./lib/abt/providers/asana.rb"
90
+ - "./lib/abt/providers/asana/api.rb"
89
91
  - "./lib/abt/providers/asana/base_command.rb"
90
- - "./lib/abt/providers/asana/clear.rb"
91
- - "./lib/abt/providers/asana/clear_global.rb"
92
- - "./lib/abt/providers/asana/current.rb"
93
- - "./lib/abt/providers/asana/harvest_link_entry_data.rb"
94
- - "./lib/abt/providers/asana/init.rb"
95
- - "./lib/abt/providers/asana/move.rb"
96
- - "./lib/abt/providers/asana/pick_task.rb"
97
- - "./lib/abt/providers/asana/projects.rb"
98
- - "./lib/abt/providers/asana/start.rb"
99
- - "./lib/abt/providers/asana/tasks.rb"
92
+ - "./lib/abt/providers/asana/commands/clear.rb"
93
+ - "./lib/abt/providers/asana/commands/clear_global.rb"
94
+ - "./lib/abt/providers/asana/commands/current.rb"
95
+ - "./lib/abt/providers/asana/commands/harvest_time_entry_data.rb"
96
+ - "./lib/abt/providers/asana/commands/init.rb"
97
+ - "./lib/abt/providers/asana/commands/move.rb"
98
+ - "./lib/abt/providers/asana/commands/pick_task.rb"
99
+ - "./lib/abt/providers/asana/commands/projects.rb"
100
+ - "./lib/abt/providers/asana/commands/start.rb"
101
+ - "./lib/abt/providers/asana/commands/tasks.rb"
102
+ - "./lib/abt/providers/asana/configuration.rb"
100
103
  - "./lib/abt/providers/harvest.rb"
101
104
  - "./lib/abt/providers/harvest/base_command.rb"
102
105
  - "./lib/abt/providers/harvest/clear.rb"
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- class AsanaClient
5
- API_ENDPOINT = 'https://app.asana.com/api/1.0'
6
- VERBS = %i[get post patch].freeze
7
-
8
- attr_reader :access_token
9
-
10
- def initialize(access_token:)
11
- @access_token = access_token
12
- end
13
-
14
- VERBS.each do |verb|
15
- define_method(verb) do |*args|
16
- request(verb, *args)['data']
17
- end
18
- end
19
-
20
- def get_paged(path, query = {})
21
- records = []
22
-
23
- loop do
24
- result = request(:get, path, query.merge(limit: 100))
25
- records += result['data']
26
- break if result['next_page'].nil?
27
-
28
- path = result['next_page']['path'][1..-1]
29
- end
30
-
31
- records
32
- end
33
-
34
- def request(*args)
35
- response = connection.public_send(*args)
36
-
37
- if response.success?
38
- Oj.load(response.body)
39
- else
40
- error_class = Abt::HttpError.error_class_for_status(response.status)
41
- encoded_response_body = response.body.force_encoding('utf-8')
42
- raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
43
- end
44
- end
45
-
46
- def connection
47
- @connection ||= Faraday.new(API_ENDPOINT) do |connection|
48
- connection.headers['Authorization'] = "Bearer #{access_token}"
49
- connection.headers['Content-Type'] = 'application/json'
50
- end
51
- end
52
- end
53
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class Clear
7
- def self.command
8
- 'clear asana'
9
- end
10
-
11
- def self.description
12
- 'Clear project/task for current git repository'
13
- end
14
-
15
- def initialize(**); end
16
-
17
- def call
18
- warn 'Clearing Asana project configuration'
19
- Asana.clear
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class ClearGlobal
7
- def self.command
8
- 'clear-global asana'
9
- end
10
-
11
- def self.description
12
- 'Clear all global configuration'
13
- end
14
-
15
- def initialize(**); end
16
-
17
- def call
18
- warn 'Clearing Asana project configuration'
19
- Asana.clear_global
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class Current < BaseCommand
7
- def self.command
8
- 'current asana[:<project-gid>[/<task-gid>]]'
9
- end
10
-
11
- def self.description
12
- 'Get or set project and or task for current git repository'
13
- end
14
-
15
- def call
16
- if arg_str.nil?
17
- show_current_configuration
18
- else
19
- warn 'Updating configuration'
20
- update_configuration
21
- end
22
- end
23
-
24
- private
25
-
26
- def show_current_configuration
27
- if project_gid.nil?
28
- warn 'No project selected'
29
- elsif task_gid.nil?
30
- print_project(project)
31
- else
32
- print_task(project, task)
33
- end
34
- end
35
-
36
- def update_configuration
37
- ensure_project_is_valid!
38
- remember_project_gid(project_gid)
39
-
40
- if task_gid.nil?
41
- print_project(project)
42
- remember_task_gid(nil)
43
- else
44
- ensure_task_is_valid!
45
- remember_task_gid(task_gid)
46
-
47
- print_task(project, task)
48
- end
49
- end
50
-
51
- def ensure_project_is_valid!
52
- abort "Invalid project: #{project_gid}" if project.nil?
53
- end
54
-
55
- def ensure_task_is_valid!
56
- abort "Invalid task: #{task_gid}" if task.nil?
57
- end
58
-
59
- def project
60
- @project ||= Asana.client.get("projects/#{project_gid}")
61
- end
62
-
63
- def task
64
- @task ||= Asana.client.get("tasks/#{task_gid}")
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class HarvestTimeEntryData < BaseCommand
7
- def self.command
8
- 'harvest-time-entry-data asana[:<project-gid>/<task-gid>]'
9
- end
10
-
11
- def self.description
12
- 'Print Harvest time entry data for Asana task as json. Used by harvest start script.'
13
- end
14
-
15
- def call # rubocop:disable Metrics/MethodLength
16
- ensure_current_is_valid!
17
-
18
- body = {
19
- notes: task['name'],
20
- external_reference: {
21
- id: task_gid.to_i,
22
- group_id: project_gid.to_i,
23
- permalink: task['permalink_url'],
24
- service: 'app.asana.com',
25
- service_icon_url: 'https://proxy.harvestfiles.com/production_harvestapp_public/uploads/platform_icons/app.asana.com.png'
26
- }
27
- }
28
-
29
- puts Oj.dump(body, mode: :json)
30
- end
31
-
32
- private
33
-
34
- def ensure_current_is_valid!
35
- abort "Invalid task gid: #{task_gid}" if task.nil?
36
-
37
- return if task['memberships'].any? { |m| m.dig('project', 'gid') == project_gid }
38
-
39
- abort "Invalid project gid: #{project_gid}"
40
- end
41
-
42
- def task
43
- @task ||= Asana.client.get("tasks/#{task_gid}")
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Abt
4
- module Providers
5
- class Asana
6
- class Init < BaseCommand
7
- def self.command
8
- 'init asana'
9
- end
10
-
11
- def self.description
12
- 'Pick Asana project for current git repository'
13
- end
14
-
15
- def call
16
- warn 'Loading projects'
17
-
18
- projects # Load projects up front to make it obvious that searches are instant
19
- project = find_search_result
20
-
21
- remember_project_gid(project['gid'])
22
- remember_task_gid(nil)
23
-
24
- print_project(project)
25
- end
26
-
27
- private
28
-
29
- def find_search_result
30
- loop do
31
- matches = matches_for_string cli.prompt('Enter search')
32
- if matches.empty?
33
- warn 'No matches'
34
- next
35
- end
36
-
37
- warn 'Showing the 10 first matches' if matches.size > 10
38
- choice = cli.prompt_choice 'Select a project', matches[0...10], true
39
- break choice unless choice.nil?
40
- end
41
- end
42
-
43
- def matches_for_string(string)
44
- search_string = sanitize_string(string)
45
-
46
- projects.select do |project|
47
- sanitize_string(project['name']).include?(search_string)
48
- end
49
- end
50
-
51
- def sanitize_string(string)
52
- string.downcase.gsub(/[^\w]/, '')
53
- end
54
-
55
- def projects
56
- @projects ||=
57
- Asana.client.get_paged('projects', workspace: Asana.workspace_gid, archived: false)
58
- end
59
- end
60
- end
61
- end
62
- end