abt-cli 0.0.18 → 0.0.19

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/abt/ari.rb +20 -0
  3. data/lib/abt/ari_list.rb +13 -0
  4. data/lib/abt/base_command.rb +63 -0
  5. data/lib/abt/cli.rb +6 -9
  6. data/lib/abt/cli/arguments_parser.rb +1 -23
  7. data/lib/abt/cli/prompt.rb +5 -4
  8. data/lib/abt/docs.rb +6 -5
  9. data/lib/abt/docs/markdown.rb +1 -1
  10. data/lib/abt/git_config.rb +20 -36
  11. data/lib/abt/providers/asana/base_command.rb +13 -33
  12. data/lib/abt/providers/asana/commands/add.rb +9 -7
  13. data/lib/abt/providers/asana/commands/branch_name.rb +9 -4
  14. data/lib/abt/providers/asana/commands/clear.rb +2 -0
  15. data/lib/abt/providers/asana/commands/current.rb +19 -34
  16. data/lib/abt/providers/asana/commands/finalize.rb +3 -3
  17. data/lib/abt/providers/asana/commands/harvest_time_entry_data.rb +9 -4
  18. data/lib/abt/providers/asana/commands/init.rb +6 -6
  19. data/lib/abt/providers/asana/commands/pick.rb +16 -11
  20. data/lib/abt/providers/asana/commands/projects.rb +1 -1
  21. data/lib/abt/providers/asana/commands/share.rb +2 -6
  22. data/lib/abt/providers/asana/commands/start.rb +14 -12
  23. data/lib/abt/providers/asana/commands/tasks.rb +4 -3
  24. data/lib/abt/providers/asana/configuration.rb +18 -24
  25. data/lib/abt/providers/asana/path.rb +36 -0
  26. data/lib/abt/providers/devops/api.rb +12 -0
  27. data/lib/abt/providers/devops/base_command.rb +13 -38
  28. data/lib/abt/providers/devops/commands/boards.rb +2 -2
  29. data/lib/abt/providers/devops/commands/branch_name.rb +7 -3
  30. data/lib/abt/providers/devops/commands/clear.rb +2 -0
  31. data/lib/abt/providers/devops/commands/current.rb +14 -38
  32. data/lib/abt/providers/devops/commands/harvest_time_entry_data.rb +9 -1
  33. data/lib/abt/providers/devops/commands/init.rb +15 -15
  34. data/lib/abt/providers/devops/commands/pick.rb +5 -12
  35. data/lib/abt/providers/devops/commands/share.rb +3 -4
  36. data/lib/abt/providers/devops/commands/work-items.rb +1 -1
  37. data/lib/abt/providers/devops/configuration.rb +17 -46
  38. data/lib/abt/providers/devops/path.rb +50 -0
  39. data/lib/abt/providers/git/commands/branch.rb +14 -8
  40. data/lib/abt/providers/harvest/base_command.rb +14 -32
  41. data/lib/abt/providers/harvest/commands/clear.rb +2 -0
  42. data/lib/abt/providers/harvest/commands/current.rb +24 -31
  43. data/lib/abt/providers/harvest/commands/init.rb +5 -6
  44. data/lib/abt/providers/harvest/commands/pick.rb +3 -4
  45. data/lib/abt/providers/harvest/commands/projects.rb +1 -1
  46. data/lib/abt/providers/harvest/commands/share.rb +4 -8
  47. data/lib/abt/providers/harvest/commands/stop.rb +7 -7
  48. data/lib/abt/providers/harvest/commands/tasks.rb +4 -1
  49. data/lib/abt/providers/harvest/commands/track.rb +25 -18
  50. data/lib/abt/providers/harvest/configuration.rb +20 -29
  51. data/lib/abt/providers/harvest/path.rb +36 -0
  52. data/lib/abt/version.rb +1 -1
  53. metadata +8 -3
  54. data/lib/abt/cli/base_command.rb +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df400d04c979d979ab2ead353d68374b8266c0fff678103991cc6443512b703b
4
- data.tar.gz: 0345141c38eae6904d11f40a3045fccd048d8009b70fe9714f5e0986f1779861
3
+ metadata.gz: 295db35510e60a5b46ea7f20caa01aaad247bb30cb60e61b912cab5f754637ac
4
+ data.tar.gz: e686eb40fc1ac88315c138eac56b96500c29c3f012b4bdb1d97f993a507f80b7
5
5
  SHA512:
6
- metadata.gz: 1979110cbd58f0bc71b83ad7211b86e17c5b078e842d4f99ca839c88a88d627ab92506736cd83b6e414cbea2ec2a82592f6e8b3f1effdbd2b57df7decd63793e
7
- data.tar.gz: 6af0e9bb436b304cda9d3acf6c86733050c9a7acffb691017d968e89e3f4e8e51b9fa4cdf5e3a85373ff2f684fa61e41afce0c1b9011d2e6b5551935b40530ea
6
+ metadata.gz: f18aa2b4f33bb6b51689522bca357ddb0fbe7a2d2e968bcc6754959f01bf6d54b9f93c387e8a050d165063cad26e6b360f245d2e7fa317bc1fbad8c0fa0d3379
7
+ data.tar.gz: ab46d17a29d4e447472c8c4d9242aa2e96bd5195bad4083d0476054f312d358ad5555526b44edacdf2512713e646435200689f82e8a097c1dfa306ff2ae1c4df
data/lib/abt/ari.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ class Ari
5
+ attr_reader :scheme, :path, :flags
6
+
7
+ def initialize(scheme:, path: nil, flags: [])
8
+ @scheme = scheme
9
+ @path = path
10
+ @flags = flags
11
+ end
12
+
13
+ def to_s
14
+ str = scheme
15
+ str += ":#{path}" if path
16
+
17
+ [str, *flags].join(' ')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ class AriList < Array
5
+ def to_s
6
+ map(&:to_s).join(' -- ')
7
+ end
8
+
9
+ def -(other)
10
+ AriList.new(to_a - other)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ class BaseCommand
5
+ extend Forwardable
6
+
7
+ def self.usage
8
+ raise NotImplementedError, 'Command classes must implement .usage'
9
+ end
10
+
11
+ def self.description
12
+ raise NotImplementedError, 'Command classes must implement .description'
13
+ end
14
+
15
+ def self.flags
16
+ []
17
+ end
18
+
19
+ attr_reader :ari, :cli, :flags
20
+
21
+ def_delegators(:@cli, :warn, :puts, :print, :abort, :exit_with_message)
22
+
23
+ def initialize(ari:, cli:)
24
+ @cli = cli
25
+ @ari = ari
26
+ @flags = parse_flags(ari.flags)
27
+ end
28
+
29
+ def perform
30
+ raise NotImplementedError, 'Command classes must implement #perform'
31
+ end
32
+
33
+ private
34
+
35
+ def parse_flags(flags)
36
+ result = {}
37
+
38
+ flag_parser.parse!(flags.dup, into: result)
39
+
40
+ exit_with_message(flag_parser.help) if result[:help]
41
+
42
+ result
43
+ rescue OptionParser::InvalidOption => e
44
+ abort e.message
45
+ end
46
+
47
+ def flag_parser
48
+ @flag_parser ||= OptionParser.new do |opts|
49
+ opts.banner = <<~TXT
50
+ #{self.class.description}
51
+
52
+ Usage: #{self.class.usage}
53
+ TXT
54
+
55
+ opts.on('-h', '--help')
56
+
57
+ self.class.flags.each do |(*flag)|
58
+ opts.on(*flag)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/abt/cli.rb CHANGED
@@ -17,7 +17,6 @@ module Abt
17
17
  @output = output
18
18
  @err_output = err_output
19
19
  @prompt = Abt::Cli::Prompt.new(output: err_output)
20
-
21
20
  @aris = ArgumentsParser.new(sanitized_piped_args + remaining_args).parse
22
21
  end
23
22
 
@@ -105,26 +104,24 @@ module Abt
105
104
 
106
105
  def process_aris
107
106
  used_schemes = []
108
- aris.each do |ari|
109
- scheme = ari.scheme
110
- path = ari.path
111
107
 
112
- if used_schemes.include?(scheme)
108
+ aris.each do |ari|
109
+ if used_schemes.include?(ari.scheme)
113
110
  warn "Dropping command for already used scheme: #{ari}"
114
111
  next
115
112
  end
116
113
 
117
- command_class = get_command_class(scheme)
114
+ command_class = get_command_class(ari.scheme)
118
115
  next if command_class.nil?
119
116
 
120
117
  print_command(command, ari) if output.isatty
121
118
  begin
122
- command_class.new(path: path, cli: self, flags: ari.flags).perform
119
+ command_class.new(ari: ari, cli: self).perform
123
120
  rescue Exit => e
124
121
  puts e.message
125
122
  end
126
123
 
127
- used_schemes << scheme
124
+ used_schemes << ari.scheme
128
125
  end
129
126
 
130
127
  return unless used_schemes.empty? && output.isatty
@@ -140,7 +137,7 @@ module Abt
140
137
  end
141
138
 
142
139
  def print_command(name, ari)
143
- warn "===== #{name} #{ari} =====".upcase
140
+ warn "===== #{name.upcase} #{ari} ====="
144
141
  end
145
142
  end
146
143
  end
@@ -3,28 +3,6 @@
3
3
  module Abt
4
4
  class Cli
5
5
  class ArgumentsParser
6
- class Ari
7
- attr_reader :scheme, :path, :flags
8
-
9
- def initialize(scheme:, path:, flags:)
10
- @scheme = scheme
11
- @path = path
12
- @flags = flags
13
- end
14
-
15
- def to_s
16
- str = scheme
17
- str += ":#{path}" if path
18
-
19
- [str, *flags].join(' ')
20
- end
21
- end
22
- class Aris < Array
23
- def to_s
24
- map(&:to_s).join(' -- ')
25
- end
26
- end
27
-
28
6
  attr_reader :arguments
29
7
 
30
8
  def initialize(arguments)
@@ -32,7 +10,7 @@ module Abt
32
10
  end
33
11
 
34
12
  def parse
35
- result = Aris.new
13
+ result = AriList.new
36
14
  rest = arguments.dup
37
15
 
38
16
  until rest.empty?
@@ -69,10 +69,11 @@ module Abt
69
69
  end
70
70
 
71
71
  def read_option_number(options_length, nil_option)
72
- output.print '('
73
- output.print options_length > 1 ? "1-#{options_length}" : '1'
74
- output.print nil_option_string(nil_option)
75
- output.print '): '
72
+ str = '('
73
+ str += options_length > 1 ? "1-#{options_length}" : '1'
74
+ str += nil_option_string(nil_option)
75
+ str += '): '
76
+ output.print str
76
77
 
77
78
  input = read_user_input
78
79
 
data/lib/abt/docs.rb CHANGED
@@ -26,14 +26,15 @@ module Abt
26
26
  'abt pick asana -d | abt track harvest' => 'Track on asana meeting task',
27
27
  'abt pick harvest -d | abt track harvest -c "Name of meeting"' => 'Track on separate harvest-task'
28
28
  },
29
- 'Command output can be piped:' => {
29
+ 'Many commands output ARIs that can be piped into other commands:' => {
30
30
  'abt tasks asana | grep -i <name of task>' => nil,
31
31
  'abt tasks asana | grep -i <name of task> | abt start' => nil
32
32
  },
33
- 'Sharing configuration:' => {
34
- 'abt share asana harvest | tr "\n" " "' => 'Print current configuration',
35
- 'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy configuration (mac only)',
36
- 'abt start <shared configuration>' => 'Start a shared configuration'
33
+ 'Sharing ARIs:' => {
34
+ 'abt share asana harvest | tr "\n" " "' => 'Print current asana and harvest ARIs on a single line',
35
+ 'abt share asana harvest | tr "\n" " " | pbcopy' => 'Copy ARIs to clipboard (mac only)',
36
+ 'abt start <ARIs from coworker>' => 'Work on a task your coworker shared with you',
37
+ 'abt current <ARIs from coworker> | abt start' => 'Set task as current, then start it'
37
38
  },
38
39
  'Flags:' => {
39
40
  'abt start harvest -c "comment"' => 'Add command flags after ARIs',
@@ -15,7 +15,7 @@ module Abt
15
15
 
16
16
  ## How does abt work?
17
17
 
18
- Abt is a hybrid af having small scripts each doing one thing:
18
+ Abt is a hybrid of having small scripts each doing one thing:
19
19
  - `start-asana --project-gid xxxx --task-gid yyyy`
20
20
  - `start-harvest --project-id aaaa --task-id bbbb`
21
21
 
@@ -6,21 +6,7 @@ module Abt
6
6
 
7
7
  class UnsafeNamespaceError < StandardError; end
8
8
 
9
- LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND = 'git config --local -l'
10
-
11
- def self.local_available?
12
- return @local_available if instance_variables.include?(:@local_available)
13
-
14
- @local_available = begin
15
- success = false
16
- Open3.popen3(LOCAL_CONFIG_AVAILABLE_CHECK_COMMAND) do |_i, _o, _e, thread|
17
- success = thread.value.success?
18
- end
19
- success
20
- end
21
- end
22
-
23
- def initialize(namespace: '', scope: 'local')
9
+ def initialize(scope = 'local', namespace = '')
24
10
  @namespace = namespace
25
11
 
26
12
  unless %w[local global].include? scope
@@ -30,6 +16,20 @@ module Abt
30
16
  @scope = scope
31
17
  end
32
18
 
19
+ def available?
20
+ unless instance_variables.include?(:available)
21
+ @available = begin
22
+ success = false
23
+ Open3.popen3(availability_check_call) do |_i, _o, _e, thread|
24
+ success = thread.value.success?
25
+ end
26
+ success
27
+ end
28
+ end
29
+
30
+ @available
31
+ end
32
+
33
33
  def [](key)
34
34
  get(key)
35
35
  end
@@ -49,26 +49,6 @@ module Abt
49
49
  `git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
50
50
  end
51
51
 
52
- def local
53
- @local ||= begin
54
- if scope == 'local'
55
- self
56
- else
57
- self.class.new(namespace: namespace, scope: 'local')
58
- end
59
- end
60
- end
61
-
62
- def global
63
- @global ||= begin
64
- if scope == 'global'
65
- self
66
- else
67
- self.class.new(namespace: namespace, scope: 'global')
68
- end
69
- end
70
- end
71
-
72
52
  def clear(output: nil)
73
53
  raise UnsafeNamespaceError, 'Keys can only be cleared within a namespace' if namespace.empty?
74
54
 
@@ -80,8 +60,12 @@ module Abt
80
60
 
81
61
  private
82
62
 
63
+ def availability_check_call
64
+ "git config --#{scope} -l"
65
+ end
66
+
83
67
  def ensure_scope_available!
84
- return if scope != 'local' || self.class.local_available?
68
+ return if available?
85
69
 
86
70
  raise StandardError, 'Local configuration is not available outside a git repository'
87
71
  end
@@ -3,63 +3,43 @@
3
3
  module Abt
4
4
  module Providers
5
5
  module Asana
6
- class BaseCommand < Abt::Cli::BaseCommand
7
- attr_reader :project_gid, :task_gid, :config
6
+ class BaseCommand < Abt::BaseCommand
7
+ extend Forwardable
8
8
 
9
- def initialize(path:, cli:, **)
9
+ attr_reader :path, :config
10
+
11
+ def_delegators(:@path, :project_gid, :task_gid)
12
+
13
+ def initialize(ari:, cli:)
10
14
  super
11
15
 
12
16
  @config = Configuration.new(cli: cli)
13
17
 
14
- if path.nil?
15
- use_current_path
16
- else
17
- use_path(path)
18
- end
18
+ @path = ari.path ? Path.new(ari.path) : config.path
19
19
  end
20
20
 
21
21
  private
22
22
 
23
23
  def require_project!
24
- cli.abort 'No current/specified project. Did you initialize Asana?' if project_gid.nil?
24
+ abort 'No current/specified project. Did you initialize Asana?' if project_gid.nil?
25
25
  end
26
26
 
27
27
  def require_task!
28
28
  if project_gid.nil?
29
- cli.abort 'No current/specified project. Did you initialize Asana and pick a task?'
29
+ abort 'No current/specified project. Did you initialize Asana and pick a task?'
30
30
  end
31
- cli.abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
32
- end
33
-
34
- def same_args_as_config?
35
- project_gid == config.project_gid && task_gid == config.task_gid
31
+ abort 'No current/specified task. Did you pick an Asana task?' if task_gid.nil?
36
32
  end
37
33
 
38
34
  def print_project(project)
39
35
  cli.print_ari('asana', project['gid'], project['name'])
40
- cli.warn project['permalink_url'] if project.key?('permalink_url') && cli.output.isatty
36
+ warn project['permalink_url'] if project.key?('permalink_url') && cli.output.isatty
41
37
  end
42
38
 
43
39
  def print_task(project, task)
44
40
  project = { 'gid' => project } if project.is_a?(String)
45
41
  cli.print_ari('asana', "#{project['gid']}/#{task['gid']}", task['name'])
46
- cli.warn task['permalink_url'] if task.key?('permalink_url') && cli.output.isatty
47
- end
48
-
49
- def use_current_path
50
- @project_gid = config.project_gid
51
- @task_gid = config.task_gid
52
- end
53
-
54
- def use_path(path)
55
- args = path.to_s.split('/')
56
- @project_gid = args[0].to_s
57
- @project_gid = nil if project_gid.empty?
58
-
59
- return if project_gid.nil?
60
-
61
- @task_gid = args[1].to_s
62
- @task_gid = nil if @task_gid.empty?
42
+ warn task['permalink_url'] if task.key?('permalink_url') && cli.output.isatty
63
43
  end
64
44
 
65
45
  def api
@@ -17,9 +17,14 @@ module Abt
17
17
  require_project!
18
18
 
19
19
  task
20
- print_task(project, task)
20
+ warn 'Task created'
21
+
22
+ if section
23
+ move_task
24
+ warn "Moved to section: #{section['name']}"
25
+ end
21
26
 
22
- move_task if section
27
+ print_task(project, task)
23
28
  end
24
29
 
25
30
  private
@@ -33,7 +38,6 @@ module Abt
33
38
  projects: [project_gid]
34
39
  }
35
40
  }
36
- cli.warn 'Creating task'
37
41
  api.post('tasks', Oj.dump(body, mode: :json))
38
42
  end
39
43
  end
@@ -53,7 +57,7 @@ module Abt
53
57
  end
54
58
 
55
59
  def project
56
- @project ||= api.get("projects/#{project_gid}")
60
+ @project ||= api.get("projects/#{project_gid}", opt_fields: 'name')
57
61
  end
58
62
 
59
63
  def section
@@ -62,10 +66,8 @@ module Abt
62
66
 
63
67
  def sections
64
68
  @sections ||= begin
65
- cli.warn 'Fetching sections...'
69
+ warn 'Fetching sections...'
66
70
  api.get_paged("projects/#{project_gid}/sections", opt_fields: 'name')
67
- rescue Abt::HttpError::HttpError
68
- []
69
71
  end
70
72
  end
71
73
  end